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($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.                         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($fp, 1024))) {
  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($fp, 1024)) {
  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($newret, array(&$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[$p2] as $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.