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