PEAR
[ class tree: PEAR ] [ index: PEAR ] [ all elements ]

Source for file Common.php

Documentation is available at Common.php

  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.  
  22. require_once 'PEAR.php';
  23. require_once 'Archive/Tar.php';
  24. require_once 'System.php';
  25. require_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.  */
  32. define('PEAR_COMMON_ERROR_INVALIDPHP'1);
  33. define('_PEAR_COMMON_PACKAGE_NAME_PREG''[A-Za-z][a-zA-Z0-9_]+');
  34. define('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
  37. define('_PEAR_COMMON_PACKAGE_VERSION_PREG''\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?');
  38. define('PEAR_COMMON_PACKAGE_VERSION_PREG''/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i');
  39.  
  40. // XXX far from perfect :-)
  41. define('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.  */
  105. class 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($toptarray('-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($str0$indent_len);
  310.         $data '';
  311.         // remove the same amount of whitespace from following lines
  312.         foreach (explode("\n"$stras $line{
  313.             if (substr($line0$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($fpfilesize($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($xpXML_OPTION_CASE_FOLDINGfalse);
  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$data1)) {
  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[$ktrim($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($fp5);
  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($oldreleasetrue);
  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($fpfilesize($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.                         PEAR::raiseError("Parser error: Invalid PHP file $file",
  1400.                             PEAR_COMMON_ERROR_INVALIDPHP);
  1401.                         return false;
  1402.                     }
  1403.                 case T_FUNCTION:
  1404.                 case T_NEW:
  1405.                 case T_EXTENDS:
  1406.                 case T_IMPLEMENTS:
  1407.                     $look_for $token;
  1408.                     continue 2;
  1409.                 case T_STRING:
  1410.                     if (version_compare(zend_version()'2.0''<')) {
  1411.                         if (in_array(strtolower($data),
  1412.                             array('public''private''protected''abstract',
  1413.                                   'interface''implements''clone''throw'
  1414.                                  )) {
  1415.                             PEAR::raiseError('Error: PHP5 packages must be packaged by php 5 PEAR');
  1416.                             return false;
  1417.                         }
  1418.                     }
  1419.                     if ($look_for == T_CLASS{
  1420.                         $current_class $data;
  1421.                         $current_class_level $brace_level;
  1422.                         $declared_classes[$current_class;
  1423.                     elseif ($look_for == T_INTERFACE{
  1424.                         $current_interface $data;
  1425.                         $current_class_level $brace_level;
  1426.                         $declared_interfaces[$current_interface;
  1427.                     elseif ($look_for == T_IMPLEMENTS{
  1428.                         $implements[$current_class$data;
  1429.                     elseif ($look_for == T_EXTENDS{
  1430.                         $extends[$current_class$data;
  1431.                     elseif ($look_for == T_FUNCTION{
  1432.                         if ($current_class{
  1433.                             $current_function = "$current_class::$data";
  1434.                             $declared_methods[$current_class][$data;
  1435.                         elseif ($current_interface{
  1436.                             $current_function = "$current_interface::$data";
  1437.                             $declared_methods[$current_interface][$data;
  1438.                         else {
  1439.                             $current_function $data;
  1440.                             $declared_functions[$current_function;
  1441.                         }
  1442.                         $current_function_level $brace_level;
  1443.                         $m = array();
  1444.                     elseif ($look_for == T_NEW{
  1445.                         $used_classes[$data= true;
  1446.                     }
  1447.                     $look_for = 0;
  1448.                     continue 2;
  1449.                 case T_VARIABLE:
  1450.                     $look_for = 0;
  1451.                     continue 2;
  1452.                 case T_DOC_COMMENT:
  1453.                 case T_COMMENT:
  1454.                     if (preg_match('!^/\*\*\s!'$data)) {
  1455.                         $lastphpdoc $data;
  1456.                         if (preg_match_all('/@nodep\s+(\S+)/'$lastphpdoc$m)) {
  1457.                             $nodeps array_merge($nodeps$m[1]);
  1458.                         }
  1459.                     }
  1460.                     continue 2;
  1461.                 case T_DOUBLE_COLON:
  1462.                     if (!($tokens[$i - 1][0== T_WHITESPACE || $tokens[$i - 1][0== T_STRING)) {
  1463.                         PEAR::raiseError("Parser error: Invalid PHP file $file",
  1464.                             PEAR_COMMON_ERROR_INVALIDPHP);
  1465.                         return false;
  1466.                     }
  1467.                     $class $tokens[$i - 1][1];
  1468.                     if (strtolower($class!= 'parent'{
  1469.                         $used_classes[$class= true;
  1470.                     }
  1471.                     continue 2;
  1472.             }
  1473.         }
  1474.         return array(
  1475.             "source_file" => $file,
  1476.             "declared_classes" => $declared_classes,
  1477.             "declared_interfaces" => $declared_interfaces,
  1478.             "declared_methods" => $declared_methods,
  1479.             "declared_functions" => $declared_functions,
  1480.             "used_classes" => array_diff(array_keys($used_classes)$nodeps),
  1481.             "inheritance" => $extends,
  1482.             "implements" => $implements,
  1483.             );
  1484.     }
  1485.  
  1486.     // }}}
  1487.     // {{{  betterStates()
  1488.  
  1489.     /**
  1490.      * Return an array containing all of the states that are more stable than
  1491.      * or equal to the passed in state
  1492.      *
  1493.      * @param string Release state
  1494.      * @param boolean Determines whether to include $state in the list
  1495.      * @return false|arrayFalse if $state is not a valid release state
  1496.      */
  1497.     function betterStates($state$include = false)
  1498.     {
  1499.         static $states = array('snapshot''devel''alpha''beta''stable');
  1500.         $i = array_search($state$states);
  1501.         if ($i === false{
  1502.             return false;
  1503.         }
  1504.         if ($include{
  1505.             $i--;
  1506.         }
  1507.         return array_slice($states$i + 1);
  1508.     }
  1509.  
  1510.     // }}}
  1511.     // {{{ detectDependencies()
  1512.  
  1513.     function detectDependencies($any$status_callback = null)
  1514.     {
  1515.         if (!function_exists("token_get_all")) {
  1516.             return false;
  1517.         }
  1518.         if (PEAR::isError($info $this->infoFromAny($any))) {
  1519.             return $this->raiseError($info);
  1520.         }
  1521.         if (!is_array($info)) {
  1522.             return false;
  1523.         }
  1524.         $deps = array();
  1525.         $used_c $decl_c $decl_f $decl_m = array();
  1526.         foreach ($info['filelist'as $file => $fa{
  1527.             $tmp $this->analyzeSourceCode($file);
  1528.             $used_c @array_merge($used_c$tmp['used_classes']);
  1529.             $decl_c @array_merge($decl_c$tmp['declared_classes']);
  1530.             $decl_f @array_merge($decl_f$tmp['declared_functions']);
  1531.             $decl_m @array_merge($decl_m$tmp['declared_methods']);
  1532.             $inheri @array_merge($inheri$tmp['inheritance']);
  1533.         }
  1534.         $used_c array_unique($used_c);
  1535.         $decl_c array_unique($decl_c);
  1536.         $undecl_c array_diff($used_c$decl_c);
  1537.         return array('used_classes' => $used_c,
  1538.                      'declared_classes' => $decl_c,
  1539.                      'declared_methods' => $decl_m,
  1540.                      'declared_functions' => $decl_f,
  1541.                      'undeclared_classes' => $undecl_c,
  1542.                      'inheritance' => $inheri,
  1543.                      );
  1544.     }
  1545.  
  1546.     // }}}
  1547.     // {{{ getUserRoles()
  1548.  
  1549.     /**
  1550.      * Get the valid roles for a PEAR package maintainer
  1551.      *
  1552.      * @return array 
  1553.      * @static
  1554.      */
  1555.     function getUserRoles()
  1556.     {
  1557.         return $GLOBALS['_PEAR_Common_maintainer_roles'];
  1558.     }
  1559.  
  1560.     // }}}
  1561.     // {{{ getReleaseStates()
  1562.  
  1563.     /**
  1564.      * Get the valid package release states of packages
  1565.      *
  1566.      * @return array 
  1567.      * @static
  1568.      */
  1569.     function getReleaseStates()
  1570.     {
  1571.         return $GLOBALS['_PEAR_Common_release_states'];
  1572.     }
  1573.  
  1574.     // }}}
  1575.     // {{{ getDependencyTypes()
  1576.  
  1577.     /**
  1578.      * Get the implemented dependency types (php, ext, pkg etc.)
  1579.      *
  1580.      * @return array 
  1581.      * @static
  1582.      */
  1583.     function getDependencyTypes()
  1584.     {
  1585.         return $GLOBALS['_PEAR_Common_dependency_types'];
  1586.     }
  1587.  
  1588.     // }}}
  1589.     // {{{ getDependencyRelations()
  1590.  
  1591.     /**
  1592.      * Get the implemented dependency relations (has, lt, ge etc.)
  1593.      *
  1594.      * @return array 
  1595.      * @static
  1596.      */
  1597.     function getDependencyRelations()
  1598.     {
  1599.         return $GLOBALS['_PEAR_Common_dependency_relations'];
  1600.     }
  1601.  
  1602.     // }}}
  1603.     // {{{ getFileRoles()
  1604.  
  1605.     /**
  1606.      * Get the implemented file roles
  1607.      *
  1608.      * @return array 
  1609.      * @static
  1610.      */
  1611.     function getFileRoles()
  1612.     {
  1613.         return $GLOBALS['_PEAR_Common_file_roles'];
  1614.     }
  1615.  
  1616.     // }}}
  1617.     // {{{ getReplacementTypes()
  1618.  
  1619.     /**
  1620.      * Get the implemented file replacement types in
  1621.      *
  1622.      * @return array 
  1623.      * @static
  1624.      */
  1625.     function getReplacementTypes()
  1626.     {
  1627.         return $GLOBALS['_PEAR_Common_replacement_types'];
  1628.     }
  1629.  
  1630.     // }}}
  1631.     // {{{ getProvideTypes()
  1632.  
  1633.     /**
  1634.      * Get the implemented file replacement types in
  1635.      *
  1636.      * @return array 
  1637.      * @static
  1638.      */
  1639.     function getProvideTypes()
  1640.     {
  1641.         return $GLOBALS['_PEAR_Common_provide_types'];
  1642.     }
  1643.  
  1644.     // }}}
  1645.     // {{{ getScriptPhases()
  1646.  
  1647.     /**
  1648.      * Get the implemented file replacement types in
  1649.      *
  1650.      * @return array 
  1651.      * @static
  1652.      */
  1653.     function getScriptPhases()
  1654.     {
  1655.         return $GLOBALS['_PEAR_Common_script_phases'];
  1656.     }
  1657.  
  1658.     // }}}
  1659.     // {{{ validPackageName()
  1660.  
  1661.     /**
  1662.      * Test whether a string contains a valid package name.
  1663.      *
  1664.      * @param string $name the package name to test
  1665.      *
  1666.      * @return bool 
  1667.      *
  1668.      * @access public
  1669.      */
  1670.     function validPackageName($name)
  1671.     {
  1672.         return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG$name);
  1673.     }
  1674.  
  1675.  
  1676.     // }}}
  1677.     // {{{ validPackageVersion()
  1678.  
  1679.     /**
  1680.      * Test whether a string contains a valid package version.
  1681.      *
  1682.      * @param string $ver the package version to test
  1683.      *
  1684.      * @return bool 
  1685.      *
  1686.      * @access public
  1687.      */
  1688.     function validPackageVersion($ver)
  1689.     {
  1690.         return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG$ver);
  1691.     }
  1692.  
  1693.  
  1694.     // }}}
  1695.  
  1696.     // {{{ downloadHttp()
  1697.  
  1698.     /**
  1699.      * Download a file through HTTP.  Considers suggested file name in
  1700.      * Content-disposition: header and can run a callback function for
  1701.      * different events.  The callback will be called with two
  1702.      * parameters: the callback type, and parameters.  The implemented
  1703.      * callback types are:
  1704.      *
  1705.      *  'setup'       called at the very beginning, parameter is a UI object
  1706.      *                that should be used for all output
  1707.      *  'message'     the parameter is a string with an informational message
  1708.      *  'saveas'      may be used to save with a different file name, the
  1709.      *                parameter is the filename that is about to be used.
  1710.      *                If a 'saveas' callback returns a non-empty string,
  1711.      *                that file name will be used as the filename instead.
  1712.      *                Note that $save_dir will not be affected by this, only
  1713.      *                the basename of the file.
  1714.      *  'start'       download is starting, parameter is number of bytes
  1715.      *                that are expected, or -1 if unknown
  1716.      *  'bytesread'   parameter is the number of bytes read so far
  1717.      *  'done'        download is complete, parameter is the total number
  1718.      *                of bytes read
  1719.      *  'connfailed'  if the TCP connection fails, this callback is called
  1720.      *                with array(host,port,errno,errmsg)
  1721.      *  'writefailed' if writing to disk fails, this callback is called
  1722.      *                with array(destfile,errmsg)
  1723.      *
  1724.      * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1725.      * setting), the proxy will be used.
  1726.      *
  1727.      * @param string  $url       the URL to download
  1728.      * @param object  $ui        PEAR_Frontend_* instance
  1729.      * @param object  $config    PEAR_Config instance
  1730.      * @param string  $save_dir  (optional) directory to save file in
  1731.      * @param mixed   $callback  (optional) function/method to call for status
  1732.      *                            updates
  1733.      *
  1734.      * @return string  Returns the full path of the downloaded file or a PEAR
  1735.      *                  error on failure.  If the error is caused by
  1736.      *                  socket-related errors, the error object will
  1737.      *                  have the fsockopen error code available through
  1738.      *                  getCode().
  1739.      *
  1740.      * @access public
  1741.      */
  1742.     function downloadHttp($url&$ui$save_dir '.'$callback = null)
  1743.     {
  1744.         if ($callback{
  1745.             call_user_func($callback'setup'array(&$ui));
  1746.         }
  1747.         if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!'$url$matches)) {
  1748.             list(,$host,,$port,$path$matches;
  1749.         }
  1750.         if (isset($this)) {
  1751.             $config &$this->config;
  1752.         else {
  1753.             $config &PEAR_Config::singleton();
  1754.         }
  1755.         $proxy_host $proxy_port $proxy_user $proxy_pass '';
  1756.         if ($proxy parse_url($config->get('http_proxy'))) {
  1757.             $proxy_host @$proxy['host'];
  1758.             $proxy_port @$proxy['port'];
  1759.             $proxy_user @$proxy['user'];
  1760.             $proxy_pass @$proxy['pass'];
  1761.  
  1762.             if ($proxy_port == ''{
  1763.                 $proxy_port = 8080;
  1764.             }
  1765.             if ($callback{
  1766.                 call_user_func($callback'message'"Using HTTP proxy $host:$port");
  1767.             }
  1768.         }
  1769.         if (empty($port)) {
  1770.             $port = 80;
  1771.         }
  1772.         if ($proxy_host != ''{
  1773.             $fp @fsockopen($proxy_host$proxy_port$errno$errstr);
  1774.             if (!$fp{
  1775.                 if ($callback{
  1776.                     call_user_func($callback'connfailed'array($proxy_host$proxy_port,
  1777.                                                                   $errno$errstr));
  1778.                 }
  1779.                 return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr"$errno);
  1780.             }
  1781.             $request = "GET $url HTTP/1.0\r\n";
  1782.         else {
  1783.             $fp @fsockopen($host$port$errno$errstr);
  1784.             if (!$fp{
  1785.                 if ($callback{
  1786.                     call_user_func($callback'connfailed'array($host$port,
  1787.                                                                   $errno$errstr));
  1788.                 }
  1789.                 return PEAR::raiseError("Connection to `$host:$port' failed: $errstr"$errno);
  1790.             }
  1791.             $request = "GET $path HTTP/1.0\r\n";
  1792.         }
  1793.         $request .= "Host: $host:$port\r\n".
  1794.             "User-Agent: PHP/".PHP_VERSION."\r\n";
  1795.         if ($proxy_host != '' && $proxy_user != ''{
  1796.             $request .= 'Proxy-Authorization: Basic ' .
  1797.                 base64_encode($proxy_user ':' $proxy_pass"\r\n";
  1798.         }
  1799.         $request .= "\r\n";
  1800.         fwrite($fp$request);
  1801.         $headers = array();
  1802.         while (trim($line fgets($fp1024))) {
  1803.             if (preg_match('/^([^:]+):\s+(.*)\s*$/'$line$matches)) {
  1804.                 $headers[strtolower($matches[1])trim($matches[2]);
  1805.             elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |'$line$matches)) {
  1806.                 if ($matches[1!= 200{
  1807.                     return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
  1808.                 }
  1809.             }
  1810.         }
  1811.         if (isset($headers['content-disposition']&&
  1812.             preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/'$headers['content-disposition']$matches)) {
  1813.             $save_as basename($matches[1]);
  1814.         else {
  1815.             $save_as basename($url);
  1816.         }
  1817.         if ($callback{
  1818.             $tmp call_user_func($callback'saveas'$save_as);
  1819.             if ($tmp{
  1820.                 $save_as $tmp;
  1821.             }
  1822.         }
  1823.         $dest_file $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1824.         if (!$wp @fopen($dest_file'wb')) {
  1825.             fclose($fp);
  1826.             if ($callback{
  1827.                 call_user_func($callback'writefailed'array($dest_file$php_errormsg));
  1828.             }
  1829.             return PEAR::raiseError("could not open $dest_file for writing");
  1830.         }
  1831.         if (isset($headers['content-length'])) {
  1832.             $length $headers['content-length'];
  1833.         else {
  1834.             $length = -1;
  1835.         }
  1836.         $bytes = 0;
  1837.         if ($callback{
  1838.             call_user_func($callback'start'array(basename($dest_file)$length));
  1839.         }
  1840.         while ($data @fread($fp1024)) {
  1841.             $bytes += strlen($data);
  1842.             if ($callback{
  1843.                 call_user_func($callback'bytesread'$bytes);
  1844.             }
  1845.             if (!@fwrite($wp$data)) {
  1846.                 fclose($fp);
  1847.                 if ($callback{
  1848.                     call_user_func($callback'writefailed'array($dest_file$php_errormsg));
  1849.                 }
  1850.                 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1851.             }
  1852.         }
  1853.         fclose($fp);
  1854.         fclose($wp);
  1855.         if ($callback{
  1856.             call_user_func($callback'done'$bytes);
  1857.         }
  1858.         return $dest_file;
  1859.     }
  1860.  
  1861.     // }}}
  1862.     // {{{ sortPkgDeps()
  1863.  
  1864.     /**
  1865.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1866.      *
  1867.      * It also removes duplicate dependencies
  1868.      * @param array 
  1869.      * @param boolean Sort packages in reverse order if true
  1870.      * @return array array of array(packagefilename, package.xml contents)
  1871.      */
  1872.     function sortPkgDeps(&$packages$uninstall = false)
  1873.     {
  1874.         $ret = array();
  1875.         if ($uninstall{
  1876.             foreach($packages as $packageinfo{
  1877.                 $ret[= array('info' => $packageinfo);
  1878.             }
  1879.         else {
  1880.             foreach($packages as $packagefile{
  1881.                 if (!is_array($packagefile)) {
  1882.                     $ret[= array('file' => $packagefile,
  1883.                                    'info' => $a $this->infoFromAny($packagefile),
  1884.                                    'pkg' => $a['package']);
  1885.                 else {
  1886.                     $ret[$packagefile;
  1887.                 }
  1888.             }
  1889.         }
  1890.         $checkdupes = array();
  1891.         $newret = array();
  1892.         foreach($ret as $i => $p{
  1893.             if (!isset($checkdupes[$p['info']['package']])) {
  1894.                 $checkdupes[$p['info']['package']][$i;
  1895.                 $newret[$p;
  1896.             }
  1897.         }
  1898.         $this->_packageSortTree $this->_getPkgDepTree($newret);
  1899.  
  1900.         $func $uninstall '_sortPkgDepsRev' '_sortPkgDeps';
  1901.         usort($newretarray(&$this$func));
  1902.         $this->_packageSortTree = null;
  1903.         $packages $newret;
  1904.     }
  1905.  
  1906.     // }}}
  1907.     // {{{ _sortPkgDeps()
  1908.  
  1909.     /**
  1910.      * Compare two package's package.xml, and sort
  1911.      * so that dependencies are installed first
  1912.      *
  1913.      * This is a crude compare, real dependency checking is done on install.
  1914.      * The only purpose this serves is to make the command-line
  1915.      * order-independent (you can list a dependent package first, and
  1916.      * installation occurs in the order required)
  1917.      * @access private
  1918.      */
  1919.     function _sortPkgDeps($p1$p2)
  1920.     {
  1921.         $p1name $p1['info']['package'];
  1922.         $p2name $p2['info']['package'];
  1923.         $p1deps $this->_getPkgDeps($p1);
  1924.         $p2deps $this->_getPkgDeps($p2);
  1925.         if (!count($p1deps&& !count($p2deps)) {
  1926.             return 0; // order makes no difference
  1927.         }
  1928.         if (!count($p1deps)) {
  1929.             return -1; // package 2 has dependencies, package 1 doesn't
  1930.         }
  1931.         if (!count($p2deps)) {
  1932.             return 1; // package 1 has dependencies, package 2 doesn't
  1933.         }
  1934.         // both have dependencies
  1935.         if (in_array($p1name$p2deps)) {
  1936.             return -1; // put package 1 first: package 2 depends on package 1
  1937.         }
  1938.         if (in_array($p2name$p1deps)) {
  1939.             return 1; // put package 2 first: package 1 depends on package 2
  1940.         }
  1941.         if ($this->_removedDependency($p1name$p2name)) {
  1942.             return -1; // put package 1 first: package 2 depends on packages that depend on package 1
  1943.         }
  1944.         if ($this->_removedDependency($p2name$p1name)) {
  1945.             return 1; // put package 2 first: package 1 depends on packages that depend on package 2
  1946.         }
  1947.         // doesn't really matter if neither depends on the other
  1948.         return 0;
  1949.     }
  1950.  
  1951.     // }}}
  1952.     // {{{ _sortPkgDepsRev()
  1953.  
  1954.     /**
  1955.      * Compare two package's package.xml, and sort
  1956.      * so that dependencies are uninstalled last
  1957.      *
  1958.      * This is a crude compare, real dependency checking is done on uninstall.
  1959.      * The only purpose this serves is to make the command-line
  1960.      * order-independent (you can list a dependency first, and
  1961.      * uninstallation occurs in the order required)
  1962.      * @access private
  1963.      */
  1964.     function _sortPkgDepsRev($p1$p2)
  1965.     {
  1966.         $p1name $p1['info']['package'];
  1967.         $p2name $p2['info']['package'];
  1968.         $p1deps $this->_getRevPkgDeps($p1);
  1969.         $p2deps $this->_getRevPkgDeps($p2);
  1970.         if (!count($p1deps&& !count($p2deps)) {
  1971.             return 0; // order makes no difference
  1972.         }
  1973.         if (!count($p1deps)) {
  1974.             return 1; // package 2 has dependencies, package 1 doesn't
  1975.         }
  1976.         if (!count($p2deps)) {
  1977.             return -1; // package 2 has dependencies, package 1 doesn't
  1978.         }
  1979.         // both have dependencies
  1980.         if (in_array($p1name$p2deps)) {
  1981.             return 1; // put package 1 last
  1982.         }
  1983.         if (in_array($p2name$p1deps)) {
  1984.             return -1; // put package 2 last
  1985.         }
  1986.         if ($this->_removedDependency($p1name$p2name)) {
  1987.             return 1; // put package 1 last: package 2 depends on packages that depend on package 1
  1988.         }
  1989.         if ($this->_removedDependency($p2name$p1name)) {
  1990.             return -1; // put package 2 last: package 1 depends on packages that depend on package 2
  1991.         }
  1992.         // doesn't really matter if neither depends on the other
  1993.         return 0;
  1994.     }
  1995.  
  1996.     // }}}
  1997.     // {{{ _getPkgDeps()
  1998.  
  1999.     /**
  2000.      * get an array of package dependency names
  2001.      * @param array 
  2002.      * @return array 
  2003.      * @access private
  2004.      */
  2005.     function _getPkgDeps($p)
  2006.     {
  2007.         if (!isset($p['info']['releases'])) {
  2008.             return $this->_getRevPkgDeps($p);
  2009.         }
  2010.         $rel array_shift($p['info']['releases']);
  2011.         if (!isset($rel['deps'])) {
  2012.             return array();
  2013.         }
  2014.         $ret = array();
  2015.         foreach($rel['deps'as $dep{
  2016.             if ($dep['type'== 'pkg'{
  2017.                 $ret[$dep['name'];
  2018.             }
  2019.         }
  2020.         return $ret;
  2021.     }
  2022.  
  2023.     // }}}
  2024.     // {{{ _getPkgDeps()
  2025.  
  2026.     /**
  2027.      * get an array representation of the package dependency tree
  2028.      * @return array 
  2029.      * @access private
  2030.      */
  2031.     function _getPkgDepTree($packages)
  2032.     {
  2033.         $tree = array();
  2034.         foreach ($packages as $p{
  2035.             $package $p['info']['package'];
  2036.             $deps $this->_getPkgDeps($p);
  2037.             $tree[$package$deps;
  2038.         }
  2039.         return $tree;
  2040.     }
  2041.  
  2042.     // }}}
  2043.     // {{{ _removedDependency($p1, $p2)
  2044.  
  2045.     /**
  2046.      * get an array of package dependency names for uninstall
  2047.      * @param string package 1 name
  2048.      * @param string package 2 name
  2049.      * @return bool 
  2050.      * @access private
  2051.      */
  2052.     function _removedDependency($p1$p2)
  2053.     {
  2054.         if (empty($this->_packageSortTree[$p2])) {
  2055.             return false;
  2056.         }
  2057.         if (!in_array($p1$this->_packageSortTree[$p2])) {
  2058.             foreach ($this->_packageSortTree[$p2as $potential{
  2059.                 if ($this->_removedDependency($p1$potential)) {
  2060.                     return true;
  2061.                 }
  2062.             }
  2063.             return false;
  2064.         }
  2065.         return true;
  2066.     }
  2067.  
  2068.     // }}}
  2069.     // {{{ _getRevPkgDeps()
  2070.  
  2071.     /**
  2072.      * get an array of package dependency names for uninstall
  2073.      * @param array 
  2074.      * @return array 
  2075.      * @access private
  2076.      */
  2077.     function _getRevPkgDeps($p)
  2078.     {
  2079.         if (!isset($p['info']['release_deps'])) {
  2080.             return array();
  2081.         }
  2082.         $ret = array();
  2083.         foreach($p['info']['release_deps'as $dep{
  2084.             if ($dep['type'== 'pkg'{
  2085.                 $ret[$dep['name'];
  2086.             }
  2087.         }
  2088.         return $ret;
  2089.     }
  2090.  
  2091.     // }}}
  2092. }
  2093.  
  2094. ?>

Documentation generated on Mon, 11 Mar 2019 14:23:55 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.