PageRenderTime 10ms CodeModel.GetById 3ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 0ms

/PEAR/PEAR/Common.php

https://bitbucket.org/kucing2k/ediassoc
PHP | 2094 lines | 1692 code | 70 blank | 332 comment | 114 complexity | d62e4853fb436e25163ad6d7df616c9e MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2//
   3// +----------------------------------------------------------------------+
   4// | PHP Version 4                                                        |
   5// +----------------------------------------------------------------------+
   6// | Copyright (c) 1997-2003 The PHP Group                                |
   7// +----------------------------------------------------------------------+
   8// | This source file is subject to version 3.0 of the PHP license,       |
   9// | that is bundled with this package in the file LICENSE, and is        |
  10// | available through the world-wide-web at the following url:           |
  11// | http://www.php.net/license/3_0.txt.                                  |
  12// | If you did not receive a copy of the PHP license and are unable to   |
  13// | obtain it through the world-wide-web, please send a note to          |
  14// | license@php.net so we can mail you a copy immediately.               |
  15// +----------------------------------------------------------------------+
  16// | Authors: Stig Bakken <ssb@php.net>                                   |
  17// |          Tomas V.V.Cox <cox@idecnet.com>                             |
  18// +----------------------------------------------------------------------+
  19//
  20// $Id: Common.php,v 1.126.2.2 2004/12/27 07:04:19 cellog Exp $
  21
  22require_once 'PEAR.php';
  23require_once 'Archive/Tar.php';
  24require_once 'System.php';
  25require_once 'PEAR/Config.php';
  26
  27// {{{ constants and globals
  28
  29/**
  30 * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
  31 */
  32define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
  33define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
  34define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '$/');
  35
  36// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
  37define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?');
  38define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i');
  39
  40// XXX far from perfect :-)
  41define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^(' . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?$/');
  42
  43/**
  44 * List of temporary files and directories registered by
  45 * PEAR_Common::addTempFile().
  46 * @var array
  47 */
  48$GLOBALS['_PEAR_Common_tempfiles'] = array();
  49
  50/**
  51 * Valid maintainer roles
  52 * @var array
  53 */
  54$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
  55
  56/**
  57 * Valid release states
  58 * @var array
  59 */
  60$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
  61
  62/**
  63 * Valid dependency types
  64 * @var array
  65 */
  66$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
  67
  68/**
  69 * Valid dependency relations
  70 * @var array
  71 */
  72$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne');
  73
  74/**
  75 * Valid file roles
  76 * @var array
  77 */
  78$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
  79
  80/**
  81 * Valid replacement types
  82 * @var array
  83 */
  84$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
  85
  86/**
  87 * Valid "provide" types
  88 * @var array
  89 */
  90$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
  91
  92/**
  93 * Valid "provide" types
  94 * @var array
  95 */
  96$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');
  97
  98// }}}
  99
 100/**
 101 * Class providing common functionality for PEAR administration classes.
 102 * @deprecated This class will disappear, and its components will be spread
 103 *             into smaller classes, like the AT&T breakup
 104 */
 105class PEAR_Common extends PEAR
 106{
 107    // {{{ properties
 108
 109    /** stack of elements, gives some sort of XML context */
 110    var $element_stack = array();
 111
 112    /** name of currently parsed XML element */
 113    var $current_element;
 114
 115    /** array of attributes of the currently parsed XML element */
 116    var $current_attributes = array();
 117
 118    /** assoc with information about a package */
 119    var $pkginfo = array();
 120
 121    /**
 122     * User Interface object (PEAR_Frontend_* class).  If null,
 123     * the log() method uses print.
 124     * @var object
 125     */
 126    var $ui = null;
 127
 128    /**
 129     * Configuration object (PEAR_Config).
 130     * @var object
 131     */
 132    var $config = null;
 133
 134    var $current_path = null;
 135
 136    /**
 137     * PEAR_SourceAnalyzer instance
 138     * @var object
 139     */
 140    var $source_analyzer = null;
 141    /**
 142     * Flag variable used to mark a valid package file
 143     * @var boolean
 144     * @access private
 145     */
 146    var $_validPackageFile;
 147
 148    // }}}
 149
 150    // {{{ constructor
 151
 152    /**
 153     * PEAR_Common constructor
 154     *
 155     * @access public
 156     */
 157    function PEAR_Common()
 158    {
 159        parent::PEAR();
 160        $this->config = &PEAR_Config::singleton();
 161        $this->debug = $this->config->get('verbose');
 162    }
 163
 164    // }}}
 165    // {{{ destructor
 166
 167    /**
 168     * PEAR_Common destructor
 169     *
 170     * @access private
 171     */
 172    function _PEAR_Common()
 173    {
 174        // doesn't work due to bug #14744
 175        //$tempfiles = $this->_tempfiles;
 176        $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
 177        while ($file = array_shift($tempfiles)) {
 178            if (@is_dir($file)) {
 179                System::rm(array('-rf', $file));
 180            } elseif (file_exists($file)) {
 181                unlink($file);
 182            }
 183        }
 184    }
 185
 186    // }}}
 187    // {{{ addTempFile()
 188
 189    /**
 190     * Register a temporary file or directory.  When the destructor is
 191     * executed, all registered temporary files and directories are
 192     * removed.
 193     *
 194     * @param string  $file  name of file or directory
 195     *
 196     * @return void
 197     *
 198     * @access public
 199     */
 200    function addTempFile($file)
 201    {
 202        $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
 203    }
 204
 205    // }}}
 206    // {{{ mkDirHier()
 207
 208    /**
 209     * Wrapper to System::mkDir(), creates a directory as well as
 210     * any necessary parent directories.
 211     *
 212     * @param string  $dir  directory name
 213     *
 214     * @return bool TRUE on success, or a PEAR error
 215     *
 216     * @access public
 217     */
 218    function mkDirHier($dir)
 219    {
 220        $this->log(2, "+ create dir $dir");
 221        return System::mkDir(array('-p', $dir));
 222    }
 223
 224    // }}}
 225    // {{{ log()
 226
 227    /**
 228     * Logging method.
 229     *
 230     * @param int    $level  log level (0 is quiet, higher is noisier)
 231     * @param string $msg    message to write to the log
 232     *
 233     * @return void
 234     *
 235     * @access public
 236     */
 237    function log($level, $msg, $append_crlf = true)
 238    {
 239        if ($this->debug >= $level) {
 240            if (is_object($this->ui)) {
 241                $this->ui->log($msg, $append_crlf);
 242            } else {
 243                print "$msg\n";
 244            }
 245        }
 246    }
 247
 248    // }}}
 249    // {{{ mkTempDir()
 250
 251    /**
 252     * Create and register a temporary directory.
 253     *
 254     * @param string $tmpdir (optional) Directory to use as tmpdir.
 255     *                       Will use system defaults (for example
 256     *                       /tmp or c:\windows\temp) if not specified
 257     *
 258     * @return string name of created directory
 259     *
 260     * @access public
 261     */
 262    function mkTempDir($tmpdir = '')
 263    {
 264        if ($tmpdir) {
 265            $topt = array('-t', $tmpdir);
 266        } else {
 267            $topt = array();
 268        }
 269        $topt = array_merge($topt, array('-d', 'pear'));
 270        if (!$tmpdir = System::mktemp($topt)) {
 271            return false;
 272        }
 273        $this->addTempFile($tmpdir);
 274        return $tmpdir;
 275    }
 276
 277    // }}}
 278    // {{{ setFrontendObject()
 279
 280    /**
 281     * Set object that represents the frontend to be used.
 282     *
 283     * @param  object Reference of the frontend object
 284     * @return void
 285     * @access public
 286     */
 287    function setFrontendObject(&$ui)
 288    {
 289        $this->ui = &$ui;
 290    }
 291
 292    // }}}
 293
 294    // {{{ _unIndent()
 295
 296    /**
 297     * Unindent given string (?)
 298     *
 299     * @param string $str The string that has to be unindented.
 300     * @return string
 301     * @access private
 302     */
 303    function _unIndent($str)
 304    {
 305        // remove leading newlines
 306        $str = preg_replace('/^[\r\n]+/', '', $str);
 307        // find whitespace at the beginning of the first line
 308        $indent_len = strspn($str, " \t");
 309        $indent = substr($str, 0, $indent_len);
 310        $data = '';
 311        // remove the same amount of whitespace from following lines
 312        foreach (explode("\n", $str) as $line) {
 313            if (substr($line, 0, $indent_len) == $indent) {
 314                $data .= substr($line, $indent_len) . "\n";
 315            }
 316        }
 317        return $data;
 318    }
 319
 320    // }}}
 321    // {{{ _element_start()
 322
 323    /**
 324     * XML parser callback for starting elements.  Used while package
 325     * format version is not yet known.
 326     *
 327     * @param resource  $xp       XML parser resource
 328     * @param string    $name     name of starting element
 329     * @param array     $attribs  element attributes, name => value
 330     *
 331     * @return void
 332     *
 333     * @access private
 334     */
 335    function _element_start($xp, $name, $attribs)
 336    {
 337        array_push($this->element_stack, $name);
 338        $this->current_element = $name;
 339        $spos = sizeof($this->element_stack) - 2;
 340        $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
 341        $this->current_attributes = $attribs;
 342        switch ($name) {
 343            case 'package': {
 344                $this->_validPackageFile = true;
 345                if (isset($attribs['version'])) {
 346                    $vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']);
 347                } else {
 348                    $vs = '1_0';
 349                }
 350                $elem_start = '_element_start_'. $vs;
 351                $elem_end = '_element_end_'. $vs;
 352                $cdata = '_pkginfo_cdata_'. $vs;
 353                if (!method_exists($this, $elem_start) ||
 354                      !method_exists($this, $elem_end) ||
 355                      !method_exists($this, $cdata)) {
 356                    $this->raiseError("No handlers for package.xml version $attribs[version]");
 357                    return;
 358                }
 359                xml_set_element_handler($xp, $elem_start, $elem_end);
 360                xml_set_character_data_handler($xp, $cdata);
 361                break;
 362            }
 363        }
 364    }
 365
 366    // }}}
 367    // {{{ _element_end()
 368
 369    /**
 370     * XML parser callback for ending elements.  Used while package
 371     * format version is not yet known.
 372     *
 373     * @param resource  $xp    XML parser resource
 374     * @param string    $name  name of ending element
 375     *
 376     * @return void
 377     *
 378     * @access private
 379     */
 380    function _element_end($xp, $name)
 381    {
 382    }
 383
 384    // }}}
 385
 386    // Support for package DTD v1.0:
 387    // {{{ _element_start_1_0()
 388
 389    /**
 390     * XML parser callback for ending elements.  Used for version 1.0
 391     * packages.
 392     *
 393     * @param resource  $xp    XML parser resource
 394     * @param string    $name  name of ending element
 395     *
 396     * @return void
 397     *
 398     * @access private
 399     */
 400    function _element_start_1_0($xp, $name, $attribs)
 401    {
 402        array_push($this->element_stack, $name);
 403        $this->current_element = $name;
 404        $spos = sizeof($this->element_stack) - 2;
 405        $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
 406        $this->current_attributes = $attribs;
 407        $this->cdata = '';
 408        switch ($name) {
 409            case 'dir':
 410                if ($this->in_changelog) {
 411                    break;
 412                }
 413                if ($attribs['name'] != '/') {
 414                    $this->dir_names[] = $attribs['name'];
 415                }
 416                if (isset($attribs['baseinstalldir'])) {
 417                    $this->dir_install = $attribs['baseinstalldir'];
 418                }
 419                if (isset($attribs['role'])) {
 420                    $this->dir_role = $attribs['role'];
 421                }
 422                break;
 423            case 'file':
 424                if ($this->in_changelog) {
 425                    break;
 426                }
 427                if (isset($attribs['name'])) {
 428                    $path = '';
 429                    if (count($this->dir_names)) {
 430                        foreach ($this->dir_names as $dir) {
 431                            $path .= $dir . DIRECTORY_SEPARATOR;
 432                        }
 433                    }
 434                    $path .= $attribs['name'];
 435                    unset($attribs['name']);
 436                    $this->current_path = $path;
 437                    $this->filelist[$path] = $attribs;
 438                    // Set the baseinstalldir only if the file don't have this attrib
 439                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
 440                        isset($this->dir_install))
 441                    {
 442                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
 443                    }
 444                    // Set the Role
 445                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
 446                        $this->filelist[$path]['role'] = $this->dir_role;
 447                    }
 448                }
 449                break;
 450            case 'replace':
 451                if (!$this->in_changelog) {
 452                    $this->filelist[$this->current_path]['replacements'][] = $attribs;
 453                }
 454                break;
 455            case 'maintainers':
 456                $this->pkginfo['maintainers'] = array();
 457                $this->m_i = 0; // maintainers array index
 458                break;
 459            case 'maintainer':
 460                // compatibility check
 461                if (!isset($this->pkginfo['maintainers'])) {
 462                    $this->pkginfo['maintainers'] = array();
 463                    $this->m_i = 0;
 464                }
 465                $this->pkginfo['maintainers'][$this->m_i] = array();
 466                $this->current_maintainer =& $this->pkginfo['maintainers'][$this->m_i];
 467                break;
 468            case 'changelog':
 469                $this->pkginfo['changelog'] = array();
 470                $this->c_i = 0; // changelog array index
 471                $this->in_changelog = true;
 472                break;
 473            case 'release':
 474                if ($this->in_changelog) {
 475                    $this->pkginfo['changelog'][$this->c_i] = array();
 476                    $this->current_release = &$this->pkginfo['changelog'][$this->c_i];
 477                } else {
 478                    $this->current_release = &$this->pkginfo;
 479                }
 480                break;
 481            case 'deps':
 482                if (!$this->in_changelog) {
 483                    $this->pkginfo['release_deps'] = array();
 484                }
 485                break;
 486            case 'dep':
 487                // dependencies array index
 488                if (!$this->in_changelog) {
 489                    $this->d_i++;
 490                    $this->pkginfo['release_deps'][$this->d_i] = $attribs;
 491                }
 492                break;
 493            case 'configureoptions':
 494                if (!$this->in_changelog) {
 495                    $this->pkginfo['configure_options'] = array();
 496                }
 497                break;
 498            case 'configureoption':
 499                if (!$this->in_changelog) {
 500                    $this->pkginfo['configure_options'][] = $attribs;
 501                }
 502                break;
 503            case 'provides':
 504                if (empty($attribs['type']) || empty($attribs['name'])) {
 505                    break;
 506                }
 507                $attribs['explicit'] = true;
 508                $this->pkginfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
 509                break;
 510        }
 511    }
 512
 513    // }}}
 514    // {{{ _element_end_1_0()
 515
 516    /**
 517     * XML parser callback for ending elements.  Used for version 1.0
 518     * packages.
 519     *
 520     * @param resource  $xp    XML parser resource
 521     * @param string    $name  name of ending element
 522     *
 523     * @return void
 524     *
 525     * @access private
 526     */
 527    function _element_end_1_0($xp, $name)
 528    {
 529        $data = trim($this->cdata);
 530        switch ($name) {
 531            case 'name':
 532                switch ($this->prev_element) {
 533                    case 'package':
 534                        // XXX should we check the package name here?
 535                        $this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data);
 536                        break;
 537                    case 'maintainer':
 538                        $this->current_maintainer['name'] = $data;
 539                        break;
 540                }
 541                break;
 542            case 'summary':
 543                $this->pkginfo['summary'] = $data;
 544                break;
 545            case 'description':
 546                $data = $this->_unIndent($this->cdata);
 547                $this->pkginfo['description'] = $data;
 548                break;
 549            case 'user':
 550                $this->current_maintainer['handle'] = $data;
 551                break;
 552            case 'email':
 553                $this->current_maintainer['email'] = $data;
 554                break;
 555            case 'role':
 556                $this->current_maintainer['role'] = $data;
 557                break;
 558            case 'version':
 559                $data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data);
 560                if ($this->in_changelog) {
 561                    $this->current_release['version'] = $data;
 562                } else {
 563                    $this->pkginfo['version'] = $data;
 564                }
 565                break;
 566            case 'date':
 567                if ($this->in_changelog) {
 568                    $this->current_release['release_date'] = $data;
 569                } else {
 570                    $this->pkginfo['release_date'] = $data;
 571                }
 572                break;
 573            case 'notes':
 574                // try to "de-indent" release notes in case someone
 575                // has been over-indenting their xml ;-)
 576                $data = $this->_unIndent($this->cdata);
 577                if ($this->in_changelog) {
 578                    $this->current_release['release_notes'] = $data;
 579                } else {
 580                    $this->pkginfo['release_notes'] = $data;
 581                }
 582                break;
 583            case 'warnings':
 584                if ($this->in_changelog) {
 585                    $this->current_release['release_warnings'] = $data;
 586                } else {
 587                    $this->pkginfo['release_warnings'] = $data;
 588                }
 589                break;
 590            case 'state':
 591                if ($this->in_changelog) {
 592                    $this->current_release['release_state'] = $data;
 593                } else {
 594                    $this->pkginfo['release_state'] = $data;
 595                }
 596                break;
 597            case 'license':
 598                if ($this->in_changelog) {
 599                    $this->current_release['release_license'] = $data;
 600                } else {
 601                    $this->pkginfo['release_license'] = $data;
 602                }
 603                break;
 604            case 'dep':
 605                if ($data && !$this->in_changelog) {
 606                    $this->pkginfo['release_deps'][$this->d_i]['name'] = $data;
 607                }
 608                break;
 609            case 'dir':
 610                if ($this->in_changelog) {
 611                    break;
 612                }
 613                array_pop($this->dir_names);
 614                break;
 615            case 'file':
 616                if ($this->in_changelog) {
 617                    break;
 618                }
 619                if ($data) {
 620                    $path = '';
 621                    if (count($this->dir_names)) {
 622                        foreach ($this->dir_names as $dir) {
 623                            $path .= $dir . DIRECTORY_SEPARATOR;
 624                        }
 625                    }
 626                    $path .= $data;
 627                    $this->filelist[$path] = $this->current_attributes;
 628                    // Set the baseinstalldir only if the file don't have this attrib
 629                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
 630                        isset($this->dir_install))
 631                    {
 632                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
 633                    }
 634                    // Set the Role
 635                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
 636                        $this->filelist[$path]['role'] = $this->dir_role;
 637                    }
 638                }
 639                break;
 640            case 'maintainer':
 641                if (empty($this->pkginfo['maintainers'][$this->m_i]['role'])) {
 642                    $this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead';
 643                }
 644                $this->m_i++;
 645                break;
 646            case 'release':
 647                if ($this->in_changelog) {
 648                    $this->c_i++;
 649                }
 650                break;
 651            case 'changelog':
 652                $this->in_changelog = false;
 653                break;
 654        }
 655        array_pop($this->element_stack);
 656        $spos = sizeof($this->element_stack) - 1;
 657        $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
 658        $this->cdata = '';
 659    }
 660
 661    // }}}
 662    // {{{ _pkginfo_cdata_1_0()
 663
 664    /**
 665     * XML parser callback for character data.  Used for version 1.0
 666     * packages.
 667     *
 668     * @param resource  $xp    XML parser resource
 669     * @param string    $name  character data
 670     *
 671     * @return void
 672     *
 673     * @access private
 674     */
 675    function _pkginfo_cdata_1_0($xp, $data)
 676    {
 677        if (isset($this->cdata)) {
 678            $this->cdata .= $data;
 679        }
 680    }
 681
 682    // }}}
 683
 684    // {{{ infoFromTgzFile()
 685
 686    /**
 687     * Returns information about a package file.  Expects the name of
 688     * a gzipped tar file as input.
 689     *
 690     * @param string  $file  name of .tgz file
 691     *
 692     * @return array  array with package information
 693     *
 694     * @access public
 695     *
 696     */
 697    function infoFromTgzFile($file)
 698    {
 699        if (!@is_file($file)) {
 700            return $this->raiseError("could not open file \"$file\"");
 701        }
 702        $tar = new Archive_Tar($file);
 703        if ($this->debug <= 1) {
 704            $tar->pushErrorHandling(PEAR_ERROR_RETURN);
 705        }
 706        $content = $tar->listContent();
 707        if ($this->debug <= 1) {
 708            $tar->popErrorHandling();
 709        }
 710        if (!is_array($content)) {
 711            $file = realpath($file);
 712            return $this->raiseError("Could not get contents of package \"$file\"".
 713                                     '. Invalid tgz file.');
 714        }
 715        $xml = null;
 716        foreach ($content as $file) {
 717            $name = $file['filename'];
 718            if ($name == 'package.xml') {
 719                $xml = $name;
 720                break;
 721            } elseif (ereg('package.xml$', $name, $match)) {
 722                $xml = $match[0];
 723                break;
 724            }
 725        }
 726        $tmpdir = System::mkTemp(array('-d', 'pear'));
 727        $this->addTempFile($tmpdir);
 728        if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
 729            return $this->raiseError('could not extract the package.xml file');
 730        }
 731        return $this->infoFromDescriptionFile("$tmpdir/$xml");
 732    }
 733
 734    // }}}
 735    // {{{ infoFromDescriptionFile()
 736
 737    /**
 738     * Returns information about a package file.  Expects the name of
 739     * a package xml file as input.
 740     *
 741     * @param string  $descfile  name of package xml file
 742     *
 743     * @return array  array with package information
 744     *
 745     * @access public
 746     *
 747     */
 748    function infoFromDescriptionFile($descfile)
 749    {
 750        if (!@is_file($descfile) || !is_readable($descfile) ||
 751             (!$fp = @fopen($descfile, 'r'))) {
 752            return $this->raiseError("Unable to open $descfile");
 753        }
 754
 755        // read the whole thing so we only get one cdata callback
 756        // for each block of cdata
 757        $data = fread($fp, filesize($descfile));
 758        return $this->infoFromString($data);
 759    }
 760
 761    // }}}
 762    // {{{ infoFromString()
 763
 764    /**
 765     * Returns information about a package file.  Expects the contents
 766     * of a package xml file as input.
 767     *
 768     * @param string  $data  name of package xml file
 769     *
 770     * @return array   array with package information
 771     *
 772     * @access public
 773     *
 774     */
 775    function infoFromString($data)
 776    {
 777        require_once('PEAR/Dependency.php');
 778        if (PEAR_Dependency::checkExtension($error, 'xml')) {
 779            return $this->raiseError($error);
 780        }
 781        $xp = @xml_parser_create();
 782        if (!$xp) {
 783            return $this->raiseError('Unable to create XML parser');
 784        }
 785        xml_set_object($xp, $this);
 786        xml_set_element_handler($xp, '_element_start', '_element_end');
 787        xml_set_character_data_handler($xp, '_pkginfo_cdata');
 788        xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
 789
 790        $this->element_stack = array();
 791        $this->pkginfo = array('provides' => array());
 792        $this->current_element = false;
 793        unset($this->dir_install);
 794        $this->pkginfo['filelist'] = array();
 795        $this->filelist =& $this->pkginfo['filelist'];
 796        $this->dir_names = array();
 797        $this->in_changelog = false;
 798        $this->d_i = 0;
 799        $this->cdata = '';
 800        $this->_validPackageFile = false;
 801
 802        if (!xml_parse($xp, $data, 1)) {
 803            $code = xml_get_error_code($xp);
 804            $msg = sprintf("XML error: %s at line %d",
 805                           xml_error_string($code),
 806                           xml_get_current_line_number($xp));
 807            xml_parser_free($xp);
 808            return $this->raiseError($msg, $code);
 809        }
 810
 811        xml_parser_free($xp);
 812
 813        if (!$this->_validPackageFile) {
 814            return $this->raiseError('Invalid Package File, no <package> tag');
 815        }
 816        foreach ($this->pkginfo as $k => $v) {
 817            if (!is_array($v)) {
 818                $this->pkginfo[$k] = trim($v);
 819            }
 820        }
 821        return $this->pkginfo;
 822    }
 823    // }}}
 824    // {{{ infoFromAny()
 825
 826    /**
 827     * Returns package information from different sources
 828     *
 829     * This method is able to extract information about a package
 830     * from a .tgz archive or from a XML package definition file.
 831     *
 832     * @access public
 833     * @param  string Filename of the source ('package.xml', '<package>.tgz')
 834     * @return string
 835     */
 836    function infoFromAny($info)
 837    {
 838        if (is_string($info) && file_exists($info)) {
 839            $tmp = substr($info, -4);
 840            if ($tmp == '.xml') {
 841                $info = $this->infoFromDescriptionFile($info);
 842            } elseif ($tmp == '.tar' || $tmp == '.tgz') {
 843                $info = $this->infoFromTgzFile($info);
 844            } else {
 845                $fp = fopen($info, "r");
 846                $test = fread($fp, 5);
 847                fclose($fp);
 848                if ($test == "<?xml") {
 849                    $info = $this->infoFromDescriptionFile($info);
 850                } else {
 851                    $info = $this->infoFromTgzFile($info);
 852                }
 853            }
 854            if (PEAR::isError($info)) {
 855                return $this->raiseError($info);
 856            }
 857        }
 858        return $info;
 859    }
 860
 861    // }}}
 862    // {{{ xmlFromInfo()
 863
 864    /**
 865     * Return an XML document based on the package info (as returned
 866     * by the PEAR_Common::infoFrom* methods).
 867     *
 868     * @param array  $pkginfo  package info
 869     *
 870     * @return string XML data
 871     *
 872     * @access public
 873     */
 874    function xmlFromInfo($pkginfo)
 875    {
 876        static $maint_map = array(
 877            "handle" => "user",
 878            "name" => "name",
 879            "email" => "email",
 880            "role" => "role",
 881            );
 882        $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
 883        $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
 884        $ret .= "<package version=\"1.0\">
 885  <name>$pkginfo[package]</name>
 886  <summary>".htmlspecialchars($pkginfo['summary'])."</summary>
 887  <description>".htmlspecialchars($pkginfo['description'])."</description>
 888  <maintainers>
 889";
 890        foreach ($pkginfo['maintainers'] as $maint) {
 891            $ret .= "    <maintainer>\n";
 892            foreach ($maint_map as $idx => $elm) {
 893                $ret .= "      <$elm>";
 894                $ret .= htmlspecialchars($maint[$idx]);
 895                $ret .= "</$elm>\n";
 896            }
 897            $ret .= "    </maintainer>\n";
 898        }
 899        $ret .= "  </maintainers>\n";
 900        $ret .= $this->_makeReleaseXml($pkginfo);
 901        if (@sizeof($pkginfo['changelog']) > 0) {
 902            $ret .= "  <changelog>\n";
 903            foreach ($pkginfo['changelog'] as $oldrelease) {
 904                $ret .= $this->_makeReleaseXml($oldrelease, true);
 905            }
 906            $ret .= "  </changelog>\n";
 907        }
 908        $ret .= "</package>\n";
 909        return $ret;
 910    }
 911
 912    // }}}
 913    // {{{ _makeReleaseXml()
 914
 915    /**
 916     * Generate part of an XML description with release information.
 917     *
 918     * @param array  $pkginfo    array with release information
 919     * @param bool   $changelog  whether the result will be in a changelog element
 920     *
 921     * @return string XML data
 922     *
 923     * @access private
 924     */
 925    function _makeReleaseXml($pkginfo, $changelog = false)
 926    {
 927        // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
 928        $indent = $changelog ? "  " : "";
 929        $ret = "$indent  <release>\n";
 930        if (!empty($pkginfo['version'])) {
 931            $ret .= "$indent    <version>$pkginfo[version]</version>\n";
 932        }
 933        if (!empty($pkginfo['release_date'])) {
 934            $ret .= "$indent    <date>$pkginfo[release_date]</date>\n";
 935        }
 936        if (!empty($pkginfo['release_license'])) {
 937            $ret .= "$indent    <license>$pkginfo[release_license]</license>\n";
 938        }
 939        if (!empty($pkginfo['release_state'])) {
 940            $ret .= "$indent    <state>$pkginfo[release_state]</state>\n";
 941        }
 942        if (!empty($pkginfo['release_notes'])) {
 943            $ret .= "$indent    <notes>".htmlspecialchars($pkginfo['release_notes'])."</notes>\n";
 944        }
 945        if (!empty($pkginfo['release_warnings'])) {
 946            $ret .= "$indent    <warnings>".htmlspecialchars($pkginfo['release_warnings'])."</warnings>\n";
 947        }
 948        if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
 949            $ret .= "$indent    <deps>\n";
 950            foreach ($pkginfo['release_deps'] as $dep) {
 951                $ret .= "$indent      <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
 952                if (isset($dep['version'])) {
 953                    $ret .= " version=\"$dep[version]\"";
 954                }
 955                if (isset($dep['optional'])) {
 956                    $ret .= " optional=\"$dep[optional]\"";
 957                }
 958                if (isset($dep['name'])) {
 959                    $ret .= ">$dep[name]</dep>\n";
 960                } else {
 961                    $ret .= "/>\n";
 962                }
 963            }
 964            $ret .= "$indent    </deps>\n";
 965        }
 966        if (isset($pkginfo['configure_options'])) {
 967            $ret .= "$indent    <configureoptions>\n";
 968            foreach ($pkginfo['configure_options'] as $c) {
 969                $ret .= "$indent      <configureoption name=\"".
 970                    htmlspecialchars($c['name']) . "\"";
 971                if (isset($c['default'])) {
 972                    $ret .= " default=\"" . htmlspecialchars($c['default']) . "\"";
 973                }
 974                $ret .= " prompt=\"" . htmlspecialchars($c['prompt']) . "\"";
 975                $ret .= "/>\n";
 976            }
 977            $ret .= "$indent    </configureoptions>\n";
 978        }
 979        if (isset($pkginfo['provides'])) {
 980            foreach ($pkginfo['provides'] as $key => $what) {
 981                $ret .= "$indent    <provides type=\"$what[type]\" ";
 982                $ret .= "name=\"$what[name]\" ";
 983                if (isset($what['extends'])) {
 984                    $ret .= "extends=\"$what[extends]\" ";
 985                }
 986                $ret .= "/>\n";
 987            }
 988        }
 989        if (isset($pkginfo['filelist'])) {
 990            $ret .= "$indent    <filelist>\n";
 991            foreach ($pkginfo['filelist'] as $file => $fa) {
 992                @$ret .= "$indent      <file role=\"$fa[role]\"";
 993                if (isset($fa['baseinstalldir'])) {
 994                    $ret .= ' baseinstalldir="' .
 995                        htmlspecialchars($fa['baseinstalldir']) . '"';
 996                }
 997                if (isset($fa['md5sum'])) {
 998                    $ret .= " md5sum=\"$fa[md5sum]\"";
 999                }
1000                if (isset($fa['platform'])) {
1001                    $ret .= " platform=\"$fa[platform]\"";
1002                }
1003                if (!empty($fa['install-as'])) {
1004                    $ret .= ' install-as="' .
1005                        htmlspecialchars($fa['install-as']) . '"';
1006                }
1007                $ret .= ' name="' . htmlspecialchars($file) . '"';
1008                if (empty($fa['replacements'])) {
1009                    $ret .= "/>\n";
1010                } else {
1011                    $ret .= ">\n";
1012                    foreach ($fa['replacements'] as $r) {
1013                        $ret .= "$indent        <replace";
1014                        foreach ($r as $k => $v) {
1015                            $ret .= " $k=\"" . htmlspecialchars($v) .'"';
1016                        }
1017                        $ret .= "/>\n";
1018                    }
1019                    @$ret .= "$indent      </file>\n";
1020                }
1021            }
1022            $ret .= "$indent    </filelist>\n";
1023        }
1024        $ret .= "$indent  </release>\n";
1025        return $ret;
1026    }
1027
1028    // }}}
1029    // {{{ validatePackageInfo()
1030
1031    /**
1032     * Validate XML package definition file.
1033     *
1034     * @param  string $info Filename of the package archive or of the
1035     *                package definition file
1036     * @param  array $errors Array that will contain the errors
1037     * @param  array $warnings Array that will contain the warnings
1038     * @param  string $dir_prefix (optional) directory where source files
1039     *                may be found, or empty if they are not available
1040     * @access public
1041     * @return boolean
1042     */
1043    function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
1044    {
1045        if (PEAR::isError($info = $this->infoFromAny($info))) {
1046            return $this->raiseError($info);
1047        }
1048        if (!is_array($info)) {
1049            return false;
1050        }
1051
1052        $errors = array();
1053        $warnings = array();
1054        if (!isset($info['package'])) {
1055            $errors[] = 'missing package name';
1056        } elseif (!$this->validPackageName($info['package'])) {
1057            $errors[] = 'invalid package name';
1058        }
1059        $this->_packageName = $pn = $info['package'];
1060
1061        if (empty($info['summary'])) {
1062            $errors[] = 'missing summary';
1063        } elseif (strpos(trim($info['summary']), "\n") !== false) {
1064            $warnings[] = 'summary should be on a single line';
1065        }
1066        if (empty($info['description'])) {
1067            $errors[] = 'missing description';
1068        }
1069        if (empty($info['release_license'])) {
1070            $errors[] = 'missing license';
1071        }
1072        if (!isset($info['version'])) {
1073            $errors[] = 'missing version';
1074        } elseif (!$this->validPackageVersion($info['version'])) {
1075            $errors[] = 'invalid package release version';
1076        }
1077        if (empty($info['release_state'])) {
1078            $errors[] = 'missing release state';
1079        } elseif (!in_array($info['release_state'], PEAR_Common::getReleaseStates())) {
1080            $errors[] = "invalid release state `$info[release_state]', should be one of: "
1081                . implode(' ', PEAR_Common::getReleaseStates());
1082        }
1083        if (empty($info['release_date'])) {
1084            $errors[] = 'missing release date';
1085        } elseif (!preg_match('/^\d{4}-\d\d-\d\d$/', $info['release_date'])) {
1086            $errors[] = "invalid release date `$info[release_date]', format is YYYY-MM-DD";
1087        }
1088        if (empty($info['release_notes'])) {
1089            $errors[] = "missing release notes";
1090        }
1091        if (empty($info['maintainers'])) {
1092            $errors[] = 'no maintainer(s)';
1093        } else {
1094            $i = 1;
1095            foreach ($info['maintainers'] as $m) {
1096                if (empty($m['handle'])) {
1097                    $errors[] = "maintainer $i: missing handle";
1098                }
1099                if (empty($m['role'])) {
1100                    $errors[] = "maintainer $i: missing role";
1101                } elseif (!in_array($m['role'], PEAR_Common::getUserRoles())) {
1102                    $errors[] = "maintainer $i: invalid role `$m[role]', should be one of: "
1103                        . implode(' ', PEAR_Common::getUserRoles());
1104                }
1105                if (empty($m['name'])) {
1106                    $errors[] = "maintainer $i: missing name";
1107                }
1108                if (empty($m['email'])) {
1109                    $errors[] = "maintainer $i: missing email";
1110                }
1111                $i++;
1112            }
1113        }
1114        if (!empty($info['release_deps'])) {
1115            $i = 1;
1116            foreach ($info['release_deps'] as $d) {
1117                if (empty($d['type'])) {
1118                    $errors[] = "dependency $i: missing type";
1119                } elseif (!in_array($d['type'], PEAR_Common::getDependencyTypes())) {
1120                    $errors[] = "dependency $i: invalid type '$d[type]', should be one of: " .
1121                        implode(' ', PEAR_Common::getDependencyTypes());
1122                }
1123                if (empty($d['rel'])) {
1124                    $errors[] = "dependency $i: missing relation";
1125                } elseif (!in_array($d['rel'], PEAR_Common::getDependencyRelations())) {
1126                    $errors[] = "dependency $i: invalid relation '$d[rel]', should be one of: "
1127                        . implode(' ', PEAR_Common::getDependencyRelations());
1128                }
1129                if (!empty($d['optional'])) {
1130                    if (!in_array($d['optional'], array('yes', 'no'))) {
1131                        $errors[] = "dependency $i: invalid relation optional attribute '$d[optional]', should be one of: yes no";
1132                    } else {
1133                        if (($d['rel'] == 'not' || $d['rel'] == 'ne') && $d['optional'] == 'yes') {
1134                            $errors[] = "dependency $i: 'not' and 'ne' dependencies cannot be " .
1135                                "optional";
1136                        }
1137                    }
1138                }
1139                if ($d['rel'] != 'not' && $d['rel'] != 'has' && empty($d['version'])) {
1140                    $warnings[] = "dependency $i: missing version";
1141                } elseif (($d['rel'] == 'not' || $d['rel'] == 'has') && !empty($d['version'])) {
1142                    $warnings[] = "dependency $i: version ignored for `$d[rel]' dependencies";
1143                }
1144                if ($d['rel'] == 'not' && !empty($d['version'])) {
1145                    $warnings[] = "dependency $i: 'not' defines a total conflict, to exclude " .
1146                        "specific versions, use 'ne'";
1147                }
1148                if ($d['type'] == 'php' && !empty($d['name'])) {
1149                    $warnings[] = "dependency $i: name ignored for php type dependencies";
1150                } elseif ($d['type'] != 'php' && empty($d['name'])) {
1151                    $errors[] = "dependency $i: missing name";
1152                }
1153                if ($d['type'] == 'php' && $d['rel'] == 'not') {
1154                    $errors[] = "dependency $i: PHP dependencies cannot use 'not' " .
1155                        "rel, use 'ne' to exclude versions";
1156                }
1157                $i++;
1158            }
1159        }
1160        if (!empty($info['configure_options'])) {
1161            $i = 1;
1162            foreach ($info['configure_options'] as $c) {
1163                if (empty($c['name'])) {
1164                    $errors[] = "configure option $i: missing name";
1165                }
1166                if (empty($c['prompt'])) {
1167                    $errors[] = "configure option $i: missing prompt";
1168                }
1169                $i++;
1170            }
1171        }
1172        if (empty($info['filelist'])) {
1173            $errors[] = 'no files';
1174        } else {
1175            foreach ($info['filelist'] as $file => $fa) {
1176                if (empty($fa['role'])) {
1177                    $errors[] = "file $file: missing role";
1178                    continue;
1179                } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
1180                    $errors[] = "file $file: invalid role, should be one of: "
1181                        . implode(' ', PEAR_Common::getFileRoles());
1182                }
1183                if ($fa['role'] == 'php' && $dir_prefix) {
1184                    $this->log(1, "Analyzing $file");
1185                    $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
1186                    if ($srcinfo) {
1187                        $this->buildProvidesArray($srcinfo);
1188                    }
1189                }
1190
1191                // (ssb) Any checks we can do for baseinstalldir?
1192                // (cox) Perhaps checks that either the target dir and
1193                //       baseInstall doesn't cointain "../../"
1194            }
1195        }
1196        $this->_packageName = $pn = $info['package'];
1197        $pnl = strlen($pn);
1198        foreach ((array)$this->pkginfo['provides'] as $key => $what) {
1199            if (isset($what['explicit'])) {
1200                // skip conformance checks if the provides entry is
1201                // specified in the package.xml file
1202                continue;
1203            }
1204            extract($what);
1205            if ($type == 'class') {
1206                if (!strncasecmp($name, $pn, $pnl)) {
1207                    continue;
1208                }
1209                $warnings[] = "in $file: class \"$name\" not prefixed with package name \"$pn\"";
1210            } elseif ($type == 'function') {
1211                if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
1212                    continue;
1213                }
1214                $warnings[] = "in $file: function \"$name\" not prefixed with package name \"$pn\"";
1215            }
1216        }
1217
1218
1219        return true;
1220    }
1221
1222    // }}}
1223    // {{{ buildProvidesArray()
1224
1225    /**
1226     * Build a "provides" array from data returned by
1227     * analyzeSourceCode().  The format of the built array is like
1228     * this:
1229     *
1230     *  array(
1231     *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
1232     *    ...
1233     *  )
1234     *
1235     *
1236     * @param array $srcinfo array with information about a source file
1237     * as returned by the analyzeSourceCode() method.
1238     *
1239     * @return void
1240     *
1241     * @access public
1242     *
1243     */
1244    function buildProvidesArray($srcinfo)
1245    {
1246        $file = basename($srcinfo['source_file']);
1247        $pn = '';
1248        if (isset($this->_packageName)) {
1249            $pn = $this->_packageName;
1250        }
1251        $pnl = strlen($pn);
1252        foreach ($srcinfo['declared_classes'] as $class) {
1253            $key = "class;$class";
1254            if (isset($this->pkginfo['provides'][$key])) {
1255                continue;
1256            }
1257            $this->pkginfo['provides'][$key] =
1258                array('file'=> $file, 'type' => 'class', 'name' => $class);
1259            if (isset($srcinfo['inheritance'][$class])) {
1260                $this->pkginfo['provides'][$key]['extends'] =
1261                    $srcinfo['inheritance'][$class];
1262            }
1263        }
1264        foreach ($srcinfo['declared_methods'] as $class => $methods) {
1265            foreach ($methods as $method) {
1266                $function = "$class::$method";
1267                $key = "function;$function";
1268                if ($method{0} == '_' || !strcasecmp($method, $class) ||
1269                    isset($this->pkginfo['provides'][$key])) {
1270                    continue;
1271                }
1272                $this->pkginfo['provides'][$key] =
1273                    array('file'=> $file, 'type' => 'function', 'name' => $function);
1274            }
1275        }
1276
1277        foreach ($srcinfo['declared_functions'] as $function) {
1278            $key = "function;$function";
1279            if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
1280                continue;
1281            }
1282            if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
1283                $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
1284            }
1285            $this->pkginfo['provides'][$key] =
1286                array('file'=> $file, 'type' => 'function', 'name' => $function);
1287        }
1288    }
1289
1290    // }}}
1291    // {{{ analyzeSourceCode()
1292
1293    /**
1294     * Analyze the source code of the given PHP file
1295     *
1296     * @param  string Filename of the PHP file
1297     * @return mixed
1298     * @access public
1299     */
1300    function analyzeSourceCode($file)
1301    {
1302        if (!function_exists("token_get_all")) {
1303            return false;
1304        }
1305        if (!defined('T_DOC_COMMENT')) {
1306            define('T_DOC_COMMENT', T_COMMENT);
1307        }
1308        if (!defined('T_INTERFACE')) {
1309            define('T_INTERFACE', -1);
1310        }
1311        if (!defined('T_IMPLEMENTS')) {
1312            define('T_IMPLEMENTS', -1);
1313        }
1314        if (!$fp = @fopen($file, "r")) {
1315            return false;
1316        }
1317        $contents = fread($fp, filesize($file));
1318        $tokens = token_get_all($contents);
1319/*
1320        for ($i = 0; $i < sizeof($tokens); $i++) {
1321            @list($token, $data) = $tokens[$i];
1322            if (is_string($token)) {
1323                var_dump($token);
1324            } else {
1325                print token_name($token) . ' ';
1326                var_dump(rtrim($data));
1327            }
1328        }
1329*/
1330        $look_for = 0;
1331        $paren_level = 0;
1332        $bracket_level = 0;
1333        $brace_level = 0;
1334        $lastphpdoc = '';
1335        $current_class = '';
1336        $current_interface = '';
1337        $current_class_level = -1;
1338        $current_function = '';
1339        $current_function_level = -1;
1340        $declared_classes = array();
1341        $declared_interfaces = array();
1342        $declared_functions = array();
1343        $declared_methods = array();
1344        $used_classes = array();
1345        $used_functions = array();
1346        $extends = array();
1347        $implements = array();
1348        $nodeps = array();
1349        $inquote = false;
1350        $interface = false;
1351        for ($i = 0; $i < sizeof($tokens); $i++) {
1352            if (is_array($tokens[$i])) {
1353                list($token, $data) = $tokens[$i];
1354            } else {
1355                $token = $tokens[$i];
1356                $data = '';
1357            }
1358            if ($inquote) {
1359                if ($token != '"') {
1360                    continue;
1361                } else {
1362                    $inquote = false;
1363                }
1364            }
1365            switch ($token) {
1366                case T_WHITESPACE:
1367                    continue;
1368                case ';':
1369                    if ($interface) {
1370                        $current_function = '';
1371                        $current_function_level = -1;
1372                    }
1373                    break;
1374                case '"':
1375                    $inquote = true;
1376                    break;
1377                case T_CURLY_OPEN:
1378                case T_DOLLAR_OPEN_CURLY_BRACES:
1379                case '{': $brace_level++; continue 2;
1380                case '}':
1381                    $brace_level--;
1382                    if ($current_class_level == $brace_level) {
1383                        $current_class = '';
1384                        $current_class_level = -1;
1385                    }
1386                    if ($current_function_level == $brace_level) {
1387                        $current_function = '';
1388                        $current_function_level = -1;
1389                    }
1390                    continue 2;
1391                case '[': $bracket_level++; continue 2;
1392                case ']': $bracket_level--; continue 2;
1393                case '(': $paren_level++;   continue 2;
1394                case ')': $paren_level--;   continue 2;
1395                case T_INTERFACE:
1396                    $interface = true;
1397                case T_CLASS:
1398                    if (($current_class_level != -1) || ($current_function_level != -1)) {
1399

Large files files are truncated, but you can click here to view the full file