1<?php 2/** 3 * PEAR_PackageFileManager is designed to create and manipulate 4 * package.xml version 1.0 only. 5 * 6 * PHP versions 5 and 7 7 * 8 * @category PEAR 9 * @package PEAR_PackageFileManager 10 * @author Greg Beaver <cellog@php.net> 11 * @copyright 2003-2015 The PEAR Group 12 * @license New BSD, Revised 13 * @link http://pear.php.net/package/PEAR_PackageFileManager 14 * @since File available since Release 0.1 15 */ 16 17/** 18 * PEAR installer 19 */ 20require_once 'PEAR/Common.php'; 21/**#@+ 22 * Error Codes 23 */ 24define('PEAR_PACKAGEFILEMANAGER_NOSTATE', 1); 25define('PEAR_PACKAGEFILEMANAGER_NOVERSION', 2); 26define('PEAR_PACKAGEFILEMANAGER_NOPKGDIR', 3); 27define('PEAR_PACKAGEFILEMANAGER_NOBASEDIR', 4); 28define('PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND', 5); 29define('PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE', 6); 30define('PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE', 7); 31define('PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE', 8); 32define('PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE', 9); 33define('PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE', 10); 34define('PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST', 11); 35define('PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS', 14); 36define('PEAR_PACKAGEFILEMANAGER_NOPACKAGE', 15); 37define('PEAR_PACKAGEFILEMANAGER_WRONG_MROLE', 16); 38define('PEAR_PACKAGEFILEMANAGER_NOSUMMARY', 17); 39define('PEAR_PACKAGEFILEMANAGER_NODESC', 18); 40define('PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS', 19); 41define('PEAR_PACKAGEFILEMANAGER_INVALID_PACKAGE', 22); 42define('PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE', 23); 43define('PEAR_PACKAGEFILEMANAGER_INVALID_ROLE', 24); 44define('PEAR_PACKAGEFILEMANAGER_PHP_NOT_PACKAGE', 25); 45define('PEAR_PACKAGEFILEMANAGER_CVS_PACKAGED', 26); 46define('PEAR_PACKAGEFILEMANAGER_NO_PHPCOMPATINFO', 27); 47define('PEAR_PACKAGEFILEMANAGER_NONOTES', 28); 48define('PEAR_PACKAGEFILEMANAGER_NOLICENSE', 29); 49 50/**#@-*/ 51/** 52 * Error messages 53 * @global array $GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'] 54 */ 55$GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'] = 56array( 57 'en' => 58 array( 59 PEAR_PACKAGEFILEMANAGER_NOSTATE => 60 'Release State (option \'state\') must by specified in PEAR_PackageFileManager ' . 61 'setOptions (snapshot|devel|alpha|beta|stable)', 62 PEAR_PACKAGEFILEMANAGER_NOVERSION => 63 'Release Version (option \'version\') must be specified in PEAR_PackageFileManager setOptions', 64 PEAR_PACKAGEFILEMANAGER_NOPKGDIR => 65 'Package source base directory (option \'packagedirectory\') must be ' . 66 'specified in PEAR_PackageFileManager setOptions', 67 PEAR_PACKAGEFILEMANAGER_NOBASEDIR => 68 'Package install base directory (option \'baseinstalldir\') must be ' . 69 'specified in PEAR_PackageFileManager setOptions', 70 PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND => 71 'Base class "%s" can\'t be located', 72 PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE => 73 'Base class "%s" can\'t be located in default or user-specified directories', 74 PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE => 75 'Failed to write package.xml file to destination directory', 76 PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE => 77 'Destination directory "%s" is unwritable', 78 PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE => 79 'Failed to copy package.xml.tmp file to package.xml', 80 PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE => 81 'Failed to open temporary file "%s" for writing', 82 PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST => 83 'package.xml file path "%s" doesn\'t exist or isn\'t a directory', 84 PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS => 85 'Run $managerclass->setOptions() before any other methods', 86 PEAR_PACKAGEFILEMANAGER_NOPACKAGE => 87 'Package Name (option \'package\') must by specified in PEAR_PackageFileManager '. 88 'setOptions to create a new package.xml', 89 PEAR_PACKAGEFILEMANAGER_NOSUMMARY => 90 'Package Summary (option \'summary\') must by specified in PEAR_PackageFileManager' . 91 ' setOptions to create a new package.xml', 92 PEAR_PACKAGEFILEMANAGER_NODESC => 93 'Detailed Package Description (option \'description\') must be' . 94 ' specified in PEAR_PackageFileManager setOptions to create a new package.xml', 95 PEAR_PACKAGEFILEMANAGER_WRONG_MROLE => 96 'Maintainer role must be one of "%s", was "%s"', 97 PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS => 98 'Add maintainers to a package before generating the package.xml', 99 PEAR_PACKAGEFILEMANAGER_INVALID_PACKAGE => 100 'Package validation failed:%s%s', 101 PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE => 102 'Replacement Type must be one of "%s", was passed "%s"', 103 PEAR_PACKAGEFILEMANAGER_INVALID_ROLE => 104 'Invalid file role passed to addRole, must be one of "%s", was passed "%s"', 105 PEAR_PACKAGEFILEMANAGER_PHP_NOT_PACKAGE => 106 'addDependency had PHP as a package, use type="php"', 107 PEAR_PACKAGEFILEMANAGER_CVS_PACKAGED => 108 'path "%path%" contains CVS directory', 109 PEAR_PACKAGEFILEMANAGER_NO_PHPCOMPATINFO => 110 'PHP_Compat is not installed, cannot detect dependencies', 111 PEAR_PACKAGEFILEMANAGER_NONOTES => 112 'Release Notes (option \'notes\') must be specified in PEAR_PackageFileManager setOptions', 113 PEAR_PACKAGEFILEMANAGER_NOLICENSE => 114 'Release License (option \'license\') must be specified in PEAR_PackageFileManager setOptions', 115 ), 116 // other language translations go here 117 ); 118/** 119 * PEAR :: PackageFileManager updates the <filelist></filelist> section 120 * of a PEAR package.xml file to reflect the current files in 121 * preparation for a release. 122 * 123 * The PEAR_PackageFileManager class uses a plugin system to generate the 124 * list of files in a package. This allows both standard recursive 125 * directory parsing (plugin type file) and more intelligent options 126 * such as the CVS browser {@link PEAR_PackageFileManager_Cvs}, which 127 * grabs all files in a local CVS checkout to create the list, ignoring 128 * any other local files. 129 * 130 * Other options include specifying roles for file extensions (all .php 131 * files are role="php", for example), roles for directories (all directories 132 * named "tests" are given role="tests" by default), and exceptions. 133 * Exceptions are specific pathnames with * and ? wildcards that match 134 * a default role, but should have another. For example, perhaps 135 * a debug.tpl template would normally be data, but should be included 136 * in the docs role. Along these lines, to exclude files entirely, 137 * use the ignore option. 138 * 139 * Required options for a release include version, baseinstalldir, state, 140 * and packagedirectory (the full path to the local location of the 141 * package to create a package.xml file for) 142 * 143 * Example usage: 144 * <code> 145 * <?php 146 * require_once('PEAR/PackageFileManager.php'); 147 * $packagexml = new PEAR_PackageFileManager; 148 * $e = $packagexml->setOptions( 149 * array('baseinstalldir' => 'PhpDocumentor', 150 * 'version' => '1.2.1', 151 * 'packagedirectory' => 'C:/Web Pages/chiara/phpdoc2/', 152 * 'state' => 'stable', 153 * 'filelistgenerator' => 'cvs', // generate from cvs, use file for directory 154 * 'notes' => 'We\'ve implemented many new and exciting features', 155 * 'ignore' => array('TODO', 'tests/'), // ignore TODO, all files in tests/ 156 * 'installexceptions' => array('phpdoc' => '/*'), // baseinstalldir ="/" for phpdoc 157 * 'dir_roles' => array('tutorials' => 'doc'), 158 * 'exceptions' => array('README' => 'doc', // README would be data, now is doc 159 * 'PHPLICENSE.txt' => 'doc'))); // same for the license 160 * if (PEAR::isError($e)) { 161 * echo $e->getMessage(); 162 * die(); 163 * } 164 * $e = $test->addPlatformException('pear-phpdoc.bat', 'windows'); 165 * if (PEAR::isError($e)) { 166 * echo $e->getMessage(); 167 * exit; 168 * } 169 * $packagexml->addRole('pkg', 'doc'); // add a new role mapping 170 * if (PEAR::isError($e)) { 171 * echo $e->getMessage(); 172 * exit; 173 * } 174 * // replace @PHP-BIN@ in this file with the path to php executable! pretty neat 175 * $e = $test->addReplacement('pear-phpdoc', 'pear-config', '@PHP-BIN@', 'php_bin'); 176 * if (PEAR::isError($e)) { 177 * echo $e->getMessage(); 178 * exit; 179 * } 180 * $e = $test->addReplacement('pear-phpdoc.bat', 'pear-config', '@PHP-BIN@', 'php_bin'); 181 * if (PEAR::isError($e)) { 182 * echo $e->getMessage(); 183 * exit; 184 * } 185 * // note use of {@link debugPackageFile()} - this is VERY important 186 * if (isset($_GET['make']) || (isset($_SERVER['argv'][2]) && 187 * $_SERVER['argv'][2] == 'make')) { 188 * $e = $packagexml->writePackageFile(); 189 * } else { 190 * $e = $packagexml->debugPackageFile(); 191 * } 192 * if (PEAR::isError($e)) { 193 * echo $e->getMessage(); 194 * die(); 195 * } 196 * ?> 197 * </code> 198 * 199 * In addition, a package.xml file can now be generated from 200 * scratch, with the usage of new options package, summary, description, and 201 * the use of the {@link addMaintainer()} method 202 * 203 * @category PEAR 204 * @package PEAR_PackageFileManager 205 * @author Greg Beaver <cellog@php.net> 206 * @copyright 2003-2015 The PEAR Group 207 * @license New BSD, Revised 208 * @version Release: 1.7.2 209 * @link http://pear.php.net/package/PEAR_PackageFileManager 210 * @since Class available since Release 0.1 211 */ 212class PEAR_PackageFileManager 213{ 214 /** 215 * Format: array(array(regexp-ready string to search for whole path, 216 * regexp-ready string to search for basename of ignore strings),...) 217 * @var false|array 218 * @access private 219 * @since 0.1 220 */ 221 var $_ignore = false; 222 223 /** 224 * Contents of the package.xml file 225 * @var string 226 * @access private 227 * @since 0.1 228 */ 229 var $_packageXml = false; 230 231 /** 232 * Contents of the original package.xml file, if any 233 * @var string 234 * @access private 235 * @since 0.9 236 */ 237 var $_oldPackageXml = false; 238 239 /** 240 * @access private 241 * @var PEAR_Common 242 * @since 0.9 243 */ 244 var $_pear; 245 246 /** 247 * List of warnings 248 * @var array 249 * @access private 250 * @since 1.1.0 251 */ 252 var $_warningStack = array(); 253 254 /** 255 * flag used to determine whether to use PHP_CompatInfo to detect deps 256 * @var boolean 257 * @access private 258 * @since 1.3.0 259 */ 260 var $_detectDependencies = false; 261 262 /** 263 * @access private 264 * @var string 265 * @since 0.1 266 */ 267 var $_options = array( 268 'packagefile' => 'package.xml', 269 'doctype' => 'http://pear.php.net/dtd/package-1.0', 270 'filelistgenerator' => 'file', 271 'license' => 'New BSD License', 272 'changelogoldtonew' => true, 273 'roles' => 274 array( 275 'h' => 'src', 276 'c' => 'src', 277 'cpp' => 'src', 278 'm4' => 'src', 279 'w32' => 'src', 280 'dll' => 'ext', 281 'php' => 'php', 282 'html' => 'doc', 283 '*' => 'data', 284 ), 285 'dir_roles' => 286 array( 287 'docs' => 'doc', 288 'examples' => 'doc', 289 'tests' => 'test', 290 ), 291 'exceptions' => array(), 292 'installexceptions' => array(), 293 'installas' => array(), 294 'platformexceptions' => array(), 295 'scriptphaseexceptions' => array(), 296 'ignore' => array(), 297 'include' => false, 298 'deps' => false, 299 'maintainers' => false, 300 'notes' => '', 301 'changelognotes' => false, 302 'outputdirectory' => false, 303 'pathtopackagefile' => false, 304 'lang' => 'en', 305 'configure_options' => array(), 306 'replacements' => array(), 307 'pearcommonclass' => false, 308 'simpleoutput' => false, 309 'addhiddenfiles' => false, 310 'cleardependencies' => false, 311 ); 312 313 /** 314 * Set package.xml generation options 315 * 316 * The options array is indexed as follows: 317 * <code> 318 * $options = array('option_name' => <optionvalue>); 319 * </code> 320 * 321 * The documentation below simplifies this description through 322 * the use of option_name without quotes 323 * 324 * Configuration options: 325 * - lang: lang controls the language in which error messages are 326 * displayed. There are currently only English error messages, 327 * but any contributed will be added over time.<br> 328 * Possible values: en (default) 329 * - packagefile: the name of the packagefile, defaults to package.xml 330 * - pathtopackagefile: the path to an existing package file to read in, 331 * if different from the packagedirectory 332 * - packagedirectory: the path to the base directory of the package. For 333 * package PEAR_PackageFileManager, this path is 334 * /path/to/pearcvs/pear/PEAR_PackageFileManager where 335 * /path/to/pearcvs is a local path on your hard drive 336 * - outputdirectory: the path in which to place the generated package.xml 337 * by default, this is ignored, and the package.xml is 338 * created in the packagedirectory 339 * - filelistgenerator: the <filelist> section plugin which will be used. 340 * In this release, there are two generator plugins, 341 * file and cvs. For details, see the docs for these 342 * plugins 343 * - usergeneratordir: For advanced users. If you write your own filelist 344 * generator plugin, use this option to tell 345 * PEAR_PackageFileManager where to find the file that 346 * contains it. If the plugin is named foo, the class 347 * must be named PEAR_PackageFileManager_Foo 348 * no matter where it is located. By default, the Foo 349 * plugin is located in PEAR/PackageFileManager/Foo.php. 350 * If you pass /path/to/foo in this option, setOptions 351 * will look for PEAR_PackageFileManager_Foo in 352 * /path/to/foo/Foo.php 353 * - doctype: Specifies the DTD of the package.xml file. Default is 354 * http://pear.php.net/dtd/package-1.0 355 * - pearcommonclass: Specifies the name of the class to instantiate, default 356 * is PEAR_PackageFileManager_ComplexGenerator or PEAR_Common, but users can 357 * override this with a custom class that implements 358 * PEAR_Common's method interface 359 * - changelogoldtonew: True if the ChangeLog should list from oldest entry to 360 * newest. Set to false if you would like new entries first 361 * - simpleoutput: True if the package.xml should not contain md5sum or <provides /> 362 * for readability 363 * - addhiddenfiles: True if you wish to add hidden files/directories that begin with . 364 * like .bashrc. This is only used by the File generator. The CVS 365 * generator will use all files in CVS regardless of format 366 * 367 * package.xml simple options: 368 * - baseinstalldir: The base directory to install this package in. For 369 * package PEAR_PackageFileManager, this is "PEAR", for 370 * package PEAR, this is "/" 371 * - license: The license this release is released under. Default is 372 * PHP License if left unspecified 373 * - notes: Release notes, any text describing what makes this release unique 374 * - changelognotes: notes for the changelog, this should be more detailed than 375 * the release notes. By default, PEAR_PackageFileManager uses 376 * the notes option for the changelog as well 377 * - version: The version number for this release. Remember the convention for 378 * numbering: initial alpha is between 0 and 1, add b<beta number> for 379 * beta as in 1.0b1, the integer portion of the version should specify 380 * backwards compatibility, as in 1.1 is backwards compatible with 1.0, 381 * but 2.0 is not backwards compatible with 1.10. Also note that 1.10 382 * is a greater release version than 1.1 (think of it as "one point ten" 383 * and "one point one"). Bugfix releases should be a third decimal as in 384 * 1.0.1, 1.0.2 385 * - package: [optional] Package name. Use this to create a new package.xml, or 386 * overwrite an existing one from another package used as a template 387 * - summary: [optional] Summary of package purpose 388 * - description: [optional] Description of package purpose. Note that the above 389 * three options are not optional when creating a new package.xml 390 * from scratch 391 * 392 * <b>WARNING</b>: all complex options that require a file path are case-sensitive 393 * 394 * package.xml complex options: 395 * - cleardependencies: since version 1.3.0, this option will erase any existing 396 * dependencies in the package.xml if set to true 397 * - ignore: an array of filenames, directory names, or wildcard expressions specifying 398 * files to exclude entirely from the package.xml. Wildcards are operating system 399 * wildcards * and ?. file*foo.php will exclude filefoo.php, fileabrfoo.php and 400 * filewho_is_thisfoo.php. file?foo.php will exclude fileafoo.php and will not 401 * exclude fileaafoo.php. test/ will exclude all directories and subdirectories of 402 * ANY directory named test encountered in directory parsing. *test* will exclude 403 * all files and directories that contain test in their name 404 * - include: an array of filenames, directory names, or wildcard expressions specifying 405 * files to include in the listing. All other files will be ignored. 406 * Wildcards are in the same format as ignore 407 * - roles: this is an array mapping file extension to install role. This 408 * specifies default behavior that can be overridden by the exceptions 409 * option and dir_roles option. use {@link addRole()} to add a new 410 * role to the pre-existing array 411 * - dir_roles: this is an array mapping directory name to install role. All 412 * files in a directory whose name matches the directory will be 413 * given the install role specified. Single files can be excluded 414 * from this using the exceptions option. The directory should be 415 * a relative path from the baseinstalldir, or "/" for the baseinstalldir 416 * - exceptions: specify file role for specific files. This array maps all files 417 * matching the exact name of a file to a role as in "file.ext" => "role" 418 * - deps: dependency array. Pass in an empty array to clear all dependencies, and use 419 * {@link addDependency()} to add new ones/replace existing ones 420 * - maintainers: maintainers array. Pass in an empty array to clear all maintainers, and 421 * use {@link addMaintainer()} to add a new maintainer/replace existing maintainer 422 * - installexceptions: array mapping of specific filenames to baseinstalldir values. Use 423 * this to force the installation of a file into another directory, 424 * such as forcing a script to be in the root scripts directory so that 425 * it will be in the path. The filename must be a relative path to the 426 * packagedirectory 427 * - platformexceptions: array mapping of specific filenames to the platform they should be 428 * installed on. Use this to specify unix-only files or windows-only 429 * files. The format of the platform string must be 430 * OS-version-cpu-extra if any more specific information is needed, 431 * and the OS must be in lower case as in "windows." The match is 432 * performed using a regular expression, but uses * and ? wildcards 433 * instead of .* and .?. Note that hpux/aix/irix/linux are all 434 * exclusive. To select non-windows, use (*ix|*ux) 435 * - scriptphaseexceptions: array mapping of scripts to their install phase. This can be 436 * one of: pre-install, post-install, pre-uninstall, post-uninstall, 437 * pre-build, post-build, pre-setup, or post-setup 438 * - installas: array mapping of specific filenames to the filename they should be installed as. 439 * Use this to specify new filenames for files that should be installed. This will 440 * often be used in conjunction with platformexceptions if there are two files for 441 * different OSes that must have the same name when installed. 442 * - replacements: array mapping of specific filenames to complex text search-and-replace that 443 * should be performed upon install. The format is: 444 * <pre> 445 * filename => array('type' => php-const|pear-config|package-info 446 * 'from' => text in file 447 * 'to' => name of variable) 448 * </pre> 449 * if type is php-const, then 'to' must be the name of a PHP Constant. 450 * If type is pear-config, then 'to' must be the name of a PEAR config 451 * variable accessible through a PEAR_Config class->get() method. If 452 * type is package-info, then 'to' must be the name of a section from 453 * the package.xml file used to install this file. 454 * - globalreplacements: a list of replacements that should be performed on every single file. 455 * The format is the same as replacements (since 1.4.0) 456 * - configure_options: array specifies build options for PECL packages (you should probably 457 * use PECL_Gen instead, but it's here for completeness) 458 * 459 * @param array $options (optional) list of generation options 460 * @param boolean $internal (optional) private function call 461 * 462 * @see PEAR_PackageFileManager_File 463 * @see PEAR_PackageFileManager_CVS 464 * @return void|PEAR_Error 465 * @throws PEAR_PACKAGEFILEMANAGER_NOSTATE 466 * @throws PEAR_PACKAGEFILEMANAGER_NOVERSION 467 * @throws PEAR_PACKAGEFILEMANAGER_NOPKGDIR 468 * @throws PEAR_PACKAGEFILEMANAGER_NOBASEDIR 469 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE 470 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND 471 * @access public 472 * @since 0.1 473 */ 474 function setOptions($options = array(), $internal = false) 475 { 476 if (!$internal) { 477 if (!isset($options['state']) || empty($options['state'])) { 478 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSTATE); 479 } 480 if (!isset($options['version']) || empty($options['version'])) { 481 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOVERSION); 482 } 483 } 484 if (!isset($options['packagedirectory']) && !$internal) { 485 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPKGDIR); 486 } elseif (isset($options['packagedirectory']) 487 && is_string($options['packagedirectory']) ) { 488 $options['packagedirectory'] = str_replace(DIRECTORY_SEPARATOR, 489 '/', 490 realpath($options['packagedirectory'])); 491 if ($options['packagedirectory']{strlen($options['packagedirectory']) - 1} != '/') { 492 $options['packagedirectory'] .= '/'; 493 } 494 } 495 if (isset($options['pathtopackagefile']) 496 && is_string($options['pathtopackagefile'])) { 497 $options['pathtopackagefile'] = str_replace(DIRECTORY_SEPARATOR, 498 '/', 499 realpath($options['pathtopackagefile'])); 500 if ($options['pathtopackagefile']{strlen($options['pathtopackagefile']) - 1} != '/') { 501 $options['pathtopackagefile'] .= '/'; 502 } 503 } 504 if (!isset($options['baseinstalldir']) && !$internal) { 505 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOBASEDIR); 506 } 507 $this->_options = array_merge($this->_options, $options); 508 if (!isset($this->_options['roles']['*'])) { 509 $this->_options['roles']['*'] = 'data'; 510 } 511 512 if (!class_exists($this->_options['pearcommonclass'])) { 513 if ($this->_options['simpleoutput']) { 514 if ($this->isIncludeable('PEAR/PackageFile/Generator/v1.php')) { 515 include_once 'PEAR/PackageFileManager/SimpleGenerator.php'; 516 $this->_options['pearcommonclass'] = 'PEAR_PackageFileManager_SimpleGenerator'; 517 } else { 518 include_once 'PEAR/PackageFileManager/XMLOutput.php'; 519 $this->_options['pearcommonclass'] = 'PEAR_PackageFileManager_XMLOutput'; 520 } 521 } else { 522 if ($this->isIncludeable('PEAR/PackageFile/Generator/v1.php')) { 523 include_once 'PEAR/PackageFileManager/ComplexGenerator.php'; 524 $this->_options['pearcommonclass'] = 'PEAR_PackageFileManager_ComplexGenerator'; 525 } else { 526 $this->_options['pearcommonclass'] = 'PEAR_Common'; 527 } 528 } 529 } 530 531 $this->_options['filelistgenerator'] = 532 ucfirst(strtolower($this->_options['filelistgenerator'])); 533 if (!$internal) { 534 $path = (is_dir($this->_options['pathtopackagefile']) ? 535 $this->_options['pathtopackagefile'] : 536 $this->_options['packagedirectory']); 537 if (PEAR::isError($res = 538 $this->_getExistingPackageXML($path, $this->_options['packagefile']))) { 539 return $res; 540 } 541 } 542 543 // file generator resource to load 544 $resource = 'PEAR/PackageFileManager/' . $this->_options['filelistgenerator'] . '.php'; 545 // file generator class name 546 $className = substr($resource, 0, -4); 547 $className = str_replace('/', '_', $className); 548 549 if (!class_exists($className)) { 550 // attempt to load the interface from the standard PEAR location 551 if ($this->isIncludeable($resource)) { 552 include_once $resource; 553 } elseif (isset($this->_options['usergeneratordir'])) { 554 // attempt to load from a user-specified directory 555 if (is_dir(realpath($this->_options['usergeneratordir']))) { 556 $this->_options['usergeneratordir'] = 557 str_replace(DIRECTORY_SEPARATOR, 558 '/', 559 realpath($this->_options['usergeneratordir'])); 560 if ($this->_options['usergeneratordir']{strlen($this->_options['usergeneratordir']) 561 - 1} != '/') { 562 $this->_options['usergeneratordir'] .= '/'; 563 } 564 } else { 565 $this->_options['usergeneratordir'] = '////'; 566 } 567 $usergenerator = $this->_options['usergeneratordir'] . 568 $this->_options['filelistgenerator'] . '.php'; 569 if (file_exists($usergenerator) && is_readable($usergenerator)) { 570 include_once $usergenerator; 571 } 572 if (!class_exists($className)) { 573 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE, 574 $className); 575 } 576 } else { 577 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND, 578 $className); 579 } 580 } 581 } 582 583 /** 584 * Import options from an existing package.xml 585 * 586 * @param string $packagefile name of package file 587 * @param array $options (optional) list of generation options 588 * 589 * @return true|PEAR_Error 590 * @access public 591 * @since 1.5.0 592 */ 593 function importOptions($packagefile, $options = array()) 594 { 595 if (count($options) == 0) { 596 // uses default options, when no custom given 597 $options = $this->_options; 598 } 599 $options['deps'] = $options['maintainers'] = false; 600 $this->setOptions($options, true); 601 if (PEAR::isError($res = $this->_getExistingPackageXML(dirname($packagefile) . 602 DIRECTORY_SEPARATOR, basename($packagefile)))) { 603 return $res; 604 } 605 $options['package'] = $this->_oldPackageXml['package']; 606 $options['summary'] = $this->_oldPackageXml['summary']; 607 $options['description'] = $this->_oldPackageXml['description']; 608 $options['date'] = $this->_oldPackageXml['release_date']; 609 $options['version'] = $this->_oldPackageXml['version']; 610 $options['license'] = $this->_oldPackageXml['release_license']; 611 $options['state'] = $this->_oldPackageXml['release_state']; 612 $options['notes'] = $this->_oldPackageXml['release_notes']; 613 $this->setOptions($options, true); 614 if (isset($this->_packageXml['release_deps'])) { 615 $this->_options['deps'] = $this->_packageXml['release_deps']; 616 } 617 $this->_options['maintainers'] = $this->_oldPackageXml['maintainers']; 618 return true; 619 } 620 621 /** 622 * Get the existing options 623 * 624 * @return array 625 * @access public 626 * @since 1.5.0 627 */ 628 function getOptions() 629 { 630 return $this->_options; 631 } 632 633 /** 634 * Add an extension/role mapping to the role mapping option 635 * 636 * Roles influence both where a file is installed and how it is installed. 637 * Files with role="data" are in a completely different directory hierarchy 638 * from the program files of role="php" 639 * 640 * In PEAR 1.3b2, these roles are 641 * - php (most common) 642 * - data 643 * - doc 644 * - test 645 * - script (gives the file an executable attribute) 646 * - src 647 * 648 * @param string $extension file extension 649 * @param string $role role 650 * 651 * @return void|PEAR_Error 652 * @throws PEAR_PACKAGEFILEMANAGER_INVALID_ROLE 653 * @access public 654 * @since 0.1 655 */ 656 function addRole($extension, $role) 657 { 658 $roles = call_user_func(array($this->_options['pearcommonclass'], 'getfileroles')); 659 if (!in_array($role, $roles)) { 660 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_INVALID_ROLE, implode($roles, ', '), $role); 661 } 662 $this->_options['roles'][$extension] = $role; 663 } 664 665 /** 666 * Add an install-time platform conditional install for a file 667 * 668 * The format of the platform string must be 669 * OS-version-cpu-extra if any more specific information is needed, 670 * and the OS must be in lower case as in "windows." The match is 671 * performed using a regular expression, but uses * and ? wildcards 672 * instead of .* and .?. Note that hpux/aix/irix/linux are all 673 * exclusive. To select non-windows, use (*ix|*ux) 674 * 675 * This information is based on eyeing the source for OS/Guess.php, so 676 * if you are unsure of what to do, read that file. 677 * 678 * @param string $path relative path of file (relative to packagedirectory option) 679 * @param string $platform platform descriptor string 680 * 681 * @return void 682 * @access public 683 * @since 0.10 684 */ 685 function addPlatformException($path, $platform) 686 { 687 if (!isset($this->_options['platformexceptions'])) { 688 $this->_options['platformexceptions'] = array(); 689 } 690 $this->_options['platformexceptions'][$path] = $platform; 691 } 692 693 /** 694 * Add a replacement option for all files, or files matching the glob pattern 695 * 696 * This sets an install-time complex search-and-replace function 697 * allowing the setting of platform-specific variables in all 698 * installed files. 699 * 700 * if $type is php-const, then $to must be the name of a PHP Constant. 701 * If $type is pear-config, then $to must be the name of a PEAR config 702 * variable accessible through a {@link PEAR_Config::get()} method. If 703 * type is package-info, then $to must be the name of a section from 704 * the package.xml file used to install this file. 705 * 706 * @param string $type variable type, either php-const, pear-config or package-info 707 * @param string $from text to replace in the source file 708 * @param string $to variable name to use for replacement 709 * 710 * @return void|PEAR_Error 711 * @throws PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE 712 * @access public 713 * @since 1.4.0 714 */ 715 function addGlobalReplacement($type, $from, $to) 716 { 717 if (!isset($this->_options['globalreplacements'])) { 718 $this->_options['globalreplacements'] = array(); 719 } 720 $types = call_user_func(array($this->_options['pearcommonclass'], 'getreplacementtypes')); 721 if (!in_array($type, $types)) { 722 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE, 723 implode($types, ', '), $type); 724 } 725 $glob = defined('GLOB_BRACE') ? glob($path, GLOB_BRACE) : glob($path); 726 if (false !== $glob) { 727 foreach ($glob as $pathItem) { 728 $this->_options['replacements'][$pathItem][] = array( 729 'type' => $type, 730 'from' => $from, 731 'to' => $to 732 ); 733 } 734 } 735 } 736 737 /** 738 * Add a replacement option for a file 739 * 740 * This sets an install-time complex search-and-replace function 741 * allowing the setting of platform-specific variables in an 742 * installed file. 743 * 744 * if $type is php-const, then $to must be the name of a PHP Constant. 745 * If $type is pear-config, then $to must be the name of a PEAR config 746 * variable accessible through a {@link PEAR_Config::get()} method. If 747 * type is package-info, then $to must be the name of a section from 748 * the package.xml file used to install this file. 749 * 750 * @param string $path relative path of file (relative to packagedirectory option) 751 * @param string $type variable type, either php-const, pear-config or package-info 752 * @param string $from text to replace in the source file 753 * @param string $to variable name to use for replacement 754 * 755 * @return void|PEAR_Error 756 * @throws PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE 757 * @access public 758 * @since 0.10 759 */ 760 function addReplacement($path, $type, $from, $to) 761 { 762 if (!isset($this->_options['replacements'])) { 763 $this->_options['replacements'] = array(); 764 } 765 $types = call_user_func(array($this->_options['pearcommonclass'], 'getreplacementtypes')); 766 if (!in_array($type, $types)) { 767 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_INVALID_REPLACETYPE, 768 implode($types, ', '), $type); 769 } 770 $this->_options['replacements'][$path][] = array('type' => $type, 'from' => $from, 'to' => $to); 771 } 772 773 /** 774 * Add a maintainer to the list of maintainers. 775 * 776 * Every maintainer must have a valid account at pear.php.net. The 777 * first parameter is the account name (for instance, cellog is the 778 * handle for Greg Beaver at pear.php.net). Every maintainer has 779 * one of four possible roles: 780 * - lead: the primary maintainer 781 * - developer: an important developer on the project 782 * - contributor: self-explanatory 783 * - helper: ditto 784 * 785 * Finally, specify the name and email of the maintainer 786 * 787 * @param string $handle username on pear.php.net of maintainer 788 * @param string $role lead|developer|contributor|helper role of maintainer 789 * @param string $name full name of maintainer 790 * @param string $email email address of maintainer 791 * 792 * @return void|PEAR_Error 793 * @access public 794 * @since 0.9 795 */ 796 function addMaintainer($handle, $role, $name, $email) 797 { 798 if (!$this->_packageXml) { 799 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 800 } 801 if (!in_array($role, $GLOBALS['_PEAR_Common_maintainer_roles'])) { 802 $cb = array($this->_options['pearcommonclass'], 'getUserRoles'); 803 if (!is_callable($cb)) { 804 $cb = array('PEAR_Common', 'getUserRoles'); 805 } 806 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_WRONG_MROLE, 807 implode(', ', call_user_func($cb)), $role); 808 } 809 if (!isset($this->_packageXml['maintainers'])) { 810 $this->_packageXml['maintainers'] = array(); 811 } 812 $found = false; 813 foreach ($this->_packageXml['maintainers'] as $index => $maintainer) { 814 if ($maintainer['handle'] == $handle) { 815 $found = $index; 816 break; 817 } 818 } 819 $maintainer = 820 array('handle' => $handle, 'role' => $role, 'name' => $name, 'email' => $email); 821 if ($found !== false) { 822 $this->_packageXml['maintainers'][$found] = $maintainer; 823 } else { 824 $this->_packageXml['maintainers'][] = $maintainer; 825 } 826 } 827 828 /** 829 * Add an install-time configuration option for building of source 830 * 831 * This option is only useful to PECL projects that are built upon 832 * installation 833 * 834 * @param string $name name of the option 835 * @param string $prompt prompt to display to the user 836 * @param string $default (optional) default value 837 * 838 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS 839 * @return void|PEAR_Error 840 * @access public 841 * @since 0.9 842 */ 843 function addConfigureOption($name, $prompt, $default = null) 844 { 845 if (!$this->_packageXml) { 846 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 847 } 848 if (!isset($this->_packageXml['configure_options'])) { 849 $this->_packageXml['configure_options'] = array(); 850 } 851 $found = false; 852 foreach ($this->_packageXml['configure_options'] as $index => $option) { 853 if ($option['name'] == $name) { 854 $found = $index; 855 break; 856 } 857 } 858 $option = array('name' => $name, 'prompt' => $prompt); 859 if (isset($default)) { 860 $option['default'] = $default; 861 } 862 if ($found !== false) { 863 $this->_packageXml['configure_options'][$found] = $option; 864 } else { 865 $this->_packageXml['configure_options'][] = $option; 866 } 867 } 868 869 /** 870 * Uses PEAR::PHP_CompatInfo package to detect dependencies (extensions, php version) 871 * 872 * @return void|PEAR_Error 873 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS 874 * @throws PEAR_PACKAGEFILEMANAGER_NO_PHPCOMPATINFO 875 * @access public 876 * @since 1.3.0 877 */ 878 function detectDependencies() 879 { 880 if (!$this->_packageXml) { 881 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 882 } 883 if (!$this->isIncludeable('PHP/CompatInfo.php')) { 884 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NO_PHPCOMPATINFO); 885 } else { 886 include_once 'PHP/CompatInfo.php'; 887 $this->_detectDependencies = true; 888 } 889 } 890 891 /** 892 * Returns whether or not a file is in the include path. 893 * 894 * @param string $file path to filename 895 * 896 * @return boolean true if the file is in the include path, false otherwise 897 * @access public 898 * @since 1.3.0 899 */ 900 function isIncludeable($file) 901 { 902 if (!defined('PATH_SEPARATOR')) { 903 define('PATH_SEPARATOR', strtolower(substr(PHP_OS, 0, 3)) == 'win' ? ';' : ':'); 904 } 905 foreach (explode(PATH_SEPARATOR, ini_get('include_path')) as $path) { 906 if (file_exists($path . DIRECTORY_SEPARATOR . $file) && 907 is_readable($path . DIRECTORY_SEPARATOR . $file)) { 908 return true; 909 } 910 } 911 return false; 912 } 913 914 /** 915 * Add a dependency on another package, or an extension/php 916 * 917 * This will overwrite an existing dependency if it is found. In 918 * other words, if a dependency on PHP 4.1.0 exists, and 919 * addDependency('php', '4.3.0', 'ge', 'php') is called, the existing 920 * dependency on PHP 4.1.0 will be overwritten with the new one on PHP 4.3.0 921 * 922 * @param string $name Dependency element name 923 * @param string $version (optional) Dependency version 924 * @param string $operator A specific operator for the version, this can be one of: 925 * 'has', 'not', 'lt', 'le', 'eq', 'ne', 'ge', or 'gt' 926 * @param string $type (optional) Dependency type. This can be one of: 927 * 'pkg', 'ext', 'php', 'prog', 'os', 'sapi', or 'zend' 928 * @param boolean $optional (optional) true if dependency is optional 929 * 930 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS 931 * @throws PEAR_PACKAGEFILEMANAGER_PHP_NOT_PACKAGE 932 * @return void|PEAR_Error 933 * @access public 934 * @since 0.1 935 */ 936 function addDependency($name, $version = false, $operator = 'ge', $type = 'pkg', $optional = false) 937 { 938 if (!$this->_packageXml) { 939 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 940 } 941 if ((strtolower($name) == 'php') && (strtolower($type) == 'pkg')) { 942 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_PHP_NOT_PACKAGE); 943 } 944 if (!isset($this->_packageXml['release_deps']) || !is_array($this->_packageXml['release_deps'])) { 945 $this->_packageXml['release_deps'] = array(); 946 } 947 $found = false; 948 foreach ($this->_packageXml['release_deps'] as $index => $dep) { 949 if ($type == 'php') { 950 if ($dep['type'] == 'php') { 951 $found = $index; 952 break; 953 } 954 } else { 955 if (isset($dep['name']) && $dep['name'] == $name && $dep['type'] == $type) { 956 $found = $index; 957 break; 958 } 959 } 960 } 961 $dep = 962 array( 963 'name' => $name, 964 'type' => $type); 965 if ($type == 'php') { 966 unset($dep['name']); 967 } 968 if ($operator) { 969 $dep['rel'] = $operator; 970 if ($dep['rel'] != 'has' && $version) { 971 $dep['version'] = $version; 972 } 973 } 974 975 if ($optional) { 976 $dep['optional'] = 'yes'; 977 } else { 978 $dep['optional'] = 'no'; 979 } 980 981 if ($found !== false) { 982 $this->_packageXml['release_deps'][$found] = $dep; // overwrite existing dependency 983 } else { 984 $this->_packageXml['release_deps'][] = $dep; // add new dependency 985 } 986 } 987 988 /** 989 * Writes the package.xml file out with the newly created <release></release> tag 990 * 991 * ALWAYS use {@link debugPackageFile} to verify that output is correct before 992 * overwriting your package.xml 993 * 994 * @param boolean $debuginterface (optional) null if no debugging, true if web interface, false if command-line 995 * 996 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS 997 * @throws PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS 998 * @throws PEAR_PACKAGEFILEMANAGER_NONOTES 999 * @throws PEAR_PACKAGEFILEMANAGER_NOLICENSE 1000 * @throws PEAR_PACKAGEFILEMANAGER_INVALID_PACKAGE 1001 * @throws PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE 1002 * @throws PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE 1003 * @throws PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE 1004 * @throws PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE 1005 * @return true|PEAR_Error 1006 * @access public 1007 * @since 0.1 1008 */ 1009 function writePackageFile($debuginterface = null) 1010 { 1011 if (!$this->_packageXml) { 1012 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 1013 } 1014 if (!isset($this->_packageXml['maintainers']) || empty($this->_packageXml['maintainers'])) { 1015 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS); 1016 } 1017 if (!isset($this->_options['notes']) || empty($this->_options['notes'])) { 1018 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NONOTES); 1019 } 1020 if (!isset($this->_options['license']) || empty($this->_options['license'])) { 1021 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOLICENSE); 1022 } 1023 extract($this->_options); 1024 $date = date('Y-m-d'); 1025 if (isset($package)) { 1026 $this->_packageXml['package'] = $package; 1027 } 1028 if (isset($summary)) { 1029 $this->_packageXml['summary'] = $summary; 1030 } 1031 if (isset($description)) { 1032 $this->_packageXml['description'] = $description; 1033 } 1034 $this->_packageXml['release_date'] = $date; 1035 $this->_packageXml['version'] = $version; 1036 $this->_packageXml['release_license'] = $license; 1037 $this->_packageXml['release_state'] = $state; 1038 $this->_packageXml['release_notes'] = $notes; 1039 1040 $PEAR_Common = $this->_options['pearcommonclass']; 1041 $this->_pear = new $PEAR_Common; 1042 if (method_exists($this->_pear, 'setPackageFileManager')) { 1043 $this->_pear->setPackageFileManager($this); 1044 } 1045 $this->_packageXml['filelist'] = $this->_getFileList(); 1046 1047 $warnings = $this->getWarnings(); 1048 if (count($warnings)) { 1049 $nl = (isset($debuginterface) && $debuginterface ? '<br />' : "\n"); 1050 foreach ($warnings as $errmsg) { 1051 echo 'WARNING: ' . $errmsg['message'] . $nl; 1052 } 1053 } 1054 if (PEAR::isError($this->_packageXml['filelist'])) { 1055 return $this->_packageXml['filelist']; 1056 } 1057 if (isset($this->_pear->pkginfo['provides'])) { 1058 $this->_packageXml['provides'] = $this->_pear->pkginfo['provides']; 1059 } 1060 if ($this->_options['simpleoutput']) { 1061 unset($this->_packageXml['provides']); 1062 } 1063 $this->_packageXml['release_deps'] = $this->_getDependencies(); 1064 $this->_updateChangeLog(); 1065 1066 $common = &$this->_pear; 1067 $warnings = $errors = array(); 1068 if (method_exists($common, 'setPackageFileManagerOptions')) { 1069 $common->setPackageFileManagerOptions($this->_options); 1070 } 1071 $packagexml = $common->xmlFromInfo($this->_packageXml); 1072 if (PEAR::isError($packagexml)) { 1073 $errs = $packagexml->getUserinfo(); 1074 if (is_array($errs)) { 1075 foreach ($errs as $error) { 1076 if ($error['level'] == 'error') { 1077 $errors[] = $error['message']; 1078 } else { 1079 $warnings[] = $error['message']; 1080 } 1081 } 1082 } 1083 } else { 1084 $common->validatePackageInfo($packagexml, $warnings, $errors, 1085 $this->_options['packagedirectory']); 1086 } 1087 if (count($errors)) { 1088 $ret = ''; 1089 $nl = (isset($debuginterface) && $debuginterface ? '<br />' : "\n"); 1090 foreach ($errors as $errmsg) { 1091 $ret .= $errmsg . $nl; 1092 } 1093 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_INVALID_PACKAGE, $nl, $ret); 1094 } 1095 if (count($warnings)) { 1096 $nl = (isset($debuginterface) && $debuginterface ? '<br />' : "\n"); 1097 foreach ($warnings as $errmsg) { 1098 echo $errmsg . $nl; 1099 } 1100 } 1101 if (!strpos($packagexml, '<!DOCTYPE')) { 1102 // hack to fix pear 1103 $packagexml = str_replace('<package version="1.0">', 1104 '<!DOCTYPE package SYSTEM "' . $this->_options['doctype'] . 1105 "\">\n<package version=\"1.0\">", 1106 $packagexml); 1107 } 1108 if (isset($debuginterface)) { 1109 if ($debuginterface) { 1110 echo '<pre>' . htmlentities($packagexml) . '</pre>'; 1111 } else { 1112 echo $packagexml; 1113 } 1114 return true; 1115 } 1116 $outputdir = ($this->_options['outputdirectory'] ? 1117 $this->_options['outputdirectory'] : $this->_options['packagedirectory']); 1118 if ((file_exists($outputdir . $this->_options['packagefile']) && 1119 is_writable($outputdir . $this->_options['packagefile'])) 1120 || 1121 @touch($outputdir . $this->_options['packagefile'])) { 1122 if ($fp = @fopen($outputdir . $this->_options['packagefile'] . '.tmp', "w")) { 1123 $written = @fwrite($fp, $packagexml); 1124 @fclose($fp); 1125 if ($written === false) { 1126 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE); 1127 } 1128 if (!@copy($outputdir . $this->_options['packagefile'] . '.tmp', 1129 $outputdir . $this->_options['packagefile'])) { 1130 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE); 1131 } else { 1132 @unlink($outputdir . $this->_options['packagefile'] . '.tmp'); 1133 return true; 1134 } 1135 } else { 1136 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE, 1137 $outputdir . $this->_options['packagefile'] . '.tmp'); 1138 } 1139 } else { 1140 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE, $outputdir); 1141 } 1142 } 1143 1144 /** 1145 * ALWAYS use this to test output before overwriting your package.xml!! 1146 * 1147 * This method instructs writePackageFile() to simply print the package.xml 1148 * to output, either command-line or web-friendly (this is automatic 1149 * based on the value of php_sapi_name()) 1150 * 1151 * @uses writePackageFile() calls with the debug parameter set based on 1152 * whether it is called from the command-line or web interface 1153 * @return true|PEAR_Error 1154 * @access public 1155 * @since 0.1 1156 */ 1157 function debugPackageFile() 1158 { 1159 $webinterface = php_sapi_name() != 'cli'; 1160 return $this->writePackageFile($webinterface); 1161 } 1162 1163 /** 1164 * Store a warning on the warning stack 1165 * 1166 * @param integer $code error code 1167 * @param array $info additional specific error info 1168 * 1169 * @return void 1170 * @access public 1171 * @since 1.1.0 1172 */ 1173 function pushWarning($code, $info) 1174 { 1175 $this->_warningStack[] = array('code' => $code, 1176 'message' => $this->_getMessage($code, $info)); 1177 } 1178 1179 /** 1180 * Retrieve the list of warnings 1181 * 1182 * @return array 1183 * @access public 1184 * @since 1.1.0 1185 */ 1186 function getWarnings() 1187 { 1188 $a = $this->_warningStack; 1189 $this->_warningStack = array(); 1190 return $a; 1191 } 1192 1193 /** 1194 * Retrieve an error message from a code 1195 * 1196 * @param integer $code error code 1197 * @param array $info additional specific error info 1198 * 1199 * @return string Error message 1200 * @access private 1201 * @since 1.1.0 1202 */ 1203 function _getMessage($code, $info) 1204 { 1205 $msg = $GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'][$this->_options['lang']][$code]; 1206 foreach ($info as $name => $value) { 1207 $msg = str_replace('%' . $name . '%', $value, $msg); 1208 } 1209 return $msg; 1210 } 1211 1212 /** 1213 * Utility function to shorten error generation code 1214 * 1215 * {@source} 1216 * 1217 * @param integer $code error code 1218 * @param string $i1 (optional) additional specific error info #1 1219 * @param string $i2 (optional) additional specific error info #2 1220 * 1221 * @return PEAR_Error 1222 * @access public 1223 * @since 0.9 1224 */ 1225 function raiseError($code, $i1 = '', $i2 = '') 1226 { 1227 return PEAR::raiseError('PEAR_PackageFileManager Error: ' . 1228 sprintf($GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'][$this->_options['lang']][$code], 1229 $i1, $i2), $code); 1230 } 1231 1232 /** 1233 * Uses {@link PEAR_Common::analyzeSourceCode()} and {@link PEAR_Common::buildProvidesArray()} 1234 * to create the <provides></provides> section of the package.xml 1235 * 1236 * @param object &$pear PEAR_Common 1237 * @param string $file path to source file 1238 * 1239 * @return void 1240 * @access private 1241 * @since 0.9 1242 */ 1243 function _addProvides(&$pear, $file) 1244 { 1245 if (!($a = $pear->analyzeSourceCode($file))) { 1246 return; 1247 } else { 1248 $pear->buildProvidesArray($a); 1249 } 1250 } 1251 1252 /** 1253 * Generates the xml from the file list 1254 * 1255 * @uses getDirTag() generate the xml from the array 1256 * @return string 1257 * @access private 1258 * @since 0.1 1259 */ 1260 function _getFileList() 1261 { 1262 $generatorclass = 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator']; 1263 $generator = new $generatorclass($this, $this->_options); 1264 if ($this->_options['simpleoutput'] && is_a($this->_pear, 'PEAR_Common')) { 1265 return $this->_getSimpleDirTag($this->_struc = $generator->getFileList()); 1266 } 1267 return $this->_getDirTag($this->_struc = $generator->getFileList()); 1268 } 1269 1270 /** 1271 * Recursively generate the <filelist> section's <dir> and <file> tags, but with 1272 * simple human-readable output 1273 * 1274 * @param array|PEAR_Error $struc the sorted directory structure, or an error 1275 * from filelist generation 1276 * @param false|string $role whether the parent directory has a role this should 1277 * inherit 1278 * @param string $_curdir indentation level 1279 * 1280 * @return array|PEAR_Error 1281 * @access private 1282 * @since 1.2.0 1283 */ 1284 function _getSimpleDirTag($struc, $role = false, $_curdir = '') 1285 { 1286 if (PEAR::isError($struc)) { 1287 return $struc; 1288 } 1289 extract($this->_options); 1290 $ret = array(); 1291 foreach ($struc as $dir => $files) { 1292 if (false && $dir === '/') { 1293 // global directory role? overrides all exceptions except file exceptions 1294 if (isset($dir_roles['/'])) { 1295 $role = $dir_roles['/']; 1296 } 1297 return array( 1298 'baseinstalldir' => $this->_options['baseinstalldir'], 1299 '##files' => $this->_getSimpleDirTag($struc[$dir], $role, ''), 1300 'name' => '/'); 1301 } else { 1302 if (!isset($files['file']) || is_array($files['file'])) { 1303 if (isset($dir_roles[$_curdir . $dir])) { 1304 $myrole = $dir_roles[$_curdir . $dir]; 1305 } else { 1306 $myrole = $role; 1307 } 1308 $ret[$dir] = array(); 1309 if ($dir == '/') { 1310 $ret[$dir]['baseinstalldir'] = $this->_options['baseinstalldir']; 1311 } 1312 $ret[$dir]['name'] = $dir; 1313 1314 $recurdir = ($_curdir == '') ? $dir . '/' : $_curdir . $dir . '/'; 1315 if ($recurdir == '//') { 1316 $recurdir = ''; 1317 } 1318 $ret[$dir]['##files'] = $this->_getSimpleDirTag($files, $myrole, $recurdir); 1319 } else { 1320 $myrole = ''; 1321 if (!$role) { 1322 $myrole = false; 1323 if (isset($exceptions[$files['path']])) { 1324 $myrole = $exceptions[$files['path']]; 1325 } elseif (isset($roles[$files['ext']])) { 1326 $myrole = $roles[$files['ext']]; 1327 } else { 1328 $myrole = $roles['*']; 1329 } 1330 } else { 1331 $myrole = $role; 1332 if (isset($exceptions[$files['path']])) { 1333 $myrole = $exceptions[$files['path']]; 1334 } 1335 } 1336 $test = explode('/', $files['path']); 1337 foreach ($test as $subpath) { 1338 if ($subpath == 'CVS') { 1339 $this->pushWarning(PEAR_PACKAGEFILEMANAGER_CVS_PACKAGED, 1340 array('path' => $files['path'])); 1341 } 1342 } 1343 $ret[$files['file']] = array('role' => $myrole); 1344 if (isset($installexceptions[$files['path']])) { 1345 $ret[$files['file']]['baseinstalldir'] = 1346 $installexceptions[$files['path']]; 1347 } 1348 if (isset($platformexceptions[$files['path']])) { 1349 $ret[$files['file']]['platform'] = $platformexceptions[$files['path']]; 1350 } 1351 if (isset($installas[$files['path']])) { 1352 $ret[$files['file']]['install-as'] = $installas[$files['path']]; 1353 } 1354 if (isset($replacements[$files['path']])) { 1355 $ret[$files['file']]['replacements'] = $replacements[$files['path']]; 1356 } 1357 if (isset($globalreplacements)) { 1358 if (!isset($ret[$files['file']]['replacements'])) { 1359 $ret[$files['file']]['replacements'] = array(); 1360 } 1361 $ret[$files['file']]['replacements'] = array_merge( 1362 $ret[$files['file']]['replacements'], $globalreplacements); 1363 } 1364 } 1365 } 1366 } 1367 return $ret; 1368 } 1369 1370 /** 1371 * Recursively generate the <filelist> section's <dir> and <file> tags 1372 * 1373 * @param array|PEAR_Error $struc the sorted directory structure, or an error 1374 * from filelist generation 1375 * @param false|string $role whether the parent directory has a role this should 1376 * inherit 1377 * @param string $_curdir indentation level 1378 * 1379 * @return array|PEAR_Error 1380 * @access private 1381 * @since 0.1 1382 */ 1383 function _getDirTag($struc, $role = false, $_curdir = '') 1384 { 1385 if (PEAR::isError($struc)) { 1386 return $struc; 1387 } 1388 extract($this->_options); 1389 $ret = array(); 1390 foreach ($struc as $dir => $files) { 1391 if ($dir === '/') { 1392 // global directory role? overrides all exceptions except file exceptions 1393 if (isset($dir_roles['/'])) { 1394 $role = $dir_roles['/']; 1395 } 1396 return $this->_getDirTag($struc[$dir], $role, ''); 1397 } else { 1398 if (!isset($files['file']) || is_array($files['file'])) { 1399 $myrole = ''; 1400 if (isset($dir_roles[$_curdir . $dir])) { 1401 $myrole = $dir_roles[$_curdir . $dir]; 1402 } elseif ($role) { 1403 $myrole = $role; 1404 } 1405 $ret = array_merge($ret, $this->_getDirTag($files, $myrole, $_curdir . $dir . '/')); 1406 } else { 1407 $myrole = ''; 1408 if (!$role) { 1409 $myrole = false; 1410 if (isset($exceptions[$files['path']])) { 1411 $myrole = $exceptions[$files['path']]; 1412 } elseif (isset($roles[$files['ext']])) { 1413 $myrole = $roles[$files['ext']]; 1414 } else { 1415 $myrole = $roles['*']; 1416 } 1417 } else { 1418 $myrole = $role; 1419 if (isset($exceptions[$files['path']])) { 1420 $myrole = $exceptions[$files['path']]; 1421 } 1422 } 1423 if (isset($installexceptions[$files['path']])) { 1424 $bi = $installexceptions[$files['path']]; 1425 } else { 1426 $bi = $this->_options['baseinstalldir']; 1427 } 1428 $test = explode('/', $files['path']); 1429 foreach ($test as $subpath) { 1430 if ($subpath == 'CVS') { 1431 $this->pushWarning(PEAR_PACKAGEFILEMANAGER_CVS_PACKAGED, array('path' => $files['path'])); 1432 } 1433 } 1434 $ret[$files['path']] = 1435 array('role' => $myrole, 1436 'baseinstalldir' => $bi, 1437 ); 1438 if (!isset($this->_options['simpleoutput'])) { 1439 $md5sum = @md5_file($this->_options['packagedirectory'] . $files['path']); 1440 if (!empty($md5sum)) { 1441 $ret[$files['path']]['md5sum'] = $md5sum; 1442 } 1443 } elseif (isset($ret[$files['path']]['md5sum'])) { 1444 unset($ret[$files['path']]['md5sum']); 1445 } 1446 if (isset($platformexceptions[$files['path']])) { 1447 $ret[$files['path']]['platform'] = $platformexceptions[$files['path']]; 1448 } 1449 if (isset($installas[$files['path']])) { 1450 $ret[$files['path']]['install-as'] = $installas[$files['path']]; 1451 } 1452 if (isset($replacements[$files['path']])) { 1453 $ret[$files['path']]['replacements'] = $replacements[$files['path']]; 1454 } 1455 if (isset($globalreplacements) && is_array($globalreplacements)) { 1456 if (!isset($ret[$files['path']]['replacements'])) { 1457 $ret[$files['path']]['replacements'] = array(); 1458 } 1459 $ret[$files['path']]['replacements'] = array_merge( 1460 $ret[$files['path']]['replacements'], $globalreplacements); 1461 } 1462 if ($myrole == 'php' && !$this->_options['simpleoutput']) { 1463 $this->_addProvides($this->_pear, $files['fullpath']); 1464 } 1465 } 1466 } 1467 } 1468 return $ret; 1469 } 1470 1471 /** 1472 * @param array $files 1473 * @param array &$ret 1474 * 1475 * @return array 1476 * @access private 1477 * @since 1.3.0 1478 */ 1479 function _traverseFileArray($files, &$ret) 1480 { 1481 foreach ($files as $file) { 1482 if (!isset($file['fullpath'])) { 1483 $this->_traverseFileArray($file, $ret); 1484 } else { 1485 $ret[] = $file['fullpath']; 1486 } 1487 } 1488 } 1489 1490 /** 1491 * Retrieve the 'deps' option passed to the constructor 1492 * 1493 * @return array|PEAR_Error 1494 * @access private 1495 * @since 0.1 1496 */ 1497 function _getDependencies() 1498 { 1499 if ($this->_detectDependencies) { 1500 $this->_traverseFileArray($this->_struc, $ret); 1501 $compatinfo = new PHP_CompatInfo(); 1502 $info = $compatinfo->parseArray($ret); 1503 1504 $ret = $this->addDependency('php', $info['version'], 'ge', 'php', false); 1505 if (is_a($ret, 'PEAR_Error')) { 1506 return $ret; 1507 } 1508 foreach ($info['extensions'] as $ext) { 1509 $this->addDependency($ext, '', 'has', 'ext', false); 1510 } 1511 } 1512 if (isset($this->_packageXml['release_deps']) && 1513 is_array($this->_packageXml['release_deps'])) { 1514 return $this->_packageXml['release_deps']; 1515 } else { 1516 return array(); 1517 } 1518 } 1519 1520 /** 1521 * Creates a changelog entry with the current release 1522 * notes and dates, or overwrites a previous creation 1523 * 1524 * @return void 1525 * @access private 1526 * @since 0.1 1527 */ 1528 function _updateChangeLog() 1529 { 1530 $curlog = $oldchangelog = false; 1531 if (!isset($this->_packageXml['changelog'])) { 1532 $changelog = array(); 1533 if (isset($this->_oldPackageXml['release_notes'])) { 1534 $changelog['release_notes'] = $this->_oldPackageXml['release_notes']; 1535 } 1536 if (isset($this->_oldPackageXml['version'])) { 1537 $changelog['version'] = $this->_oldPackageXml['version']; 1538 } 1539 if (isset($this->_oldPackageXml['release_date'])) { 1540 $changelog['release_date'] = $this->_oldPackageXml['release_date']; 1541 } 1542 if (isset($this->_oldPackageXml['release_license'])) { 1543 $changelog['release_license'] = $this->_oldPackageXml['release_license']; 1544 } 1545 if (isset($this->_oldPackageXml['release_state'])) { 1546 $changelog['release_state'] = $this->_oldPackageXml['release_state']; 1547 } 1548 if (count($changelog)) { 1549 $this->_packageXml['changelog'] = array($changelog); 1550 } else { 1551 $this->_packageXml['changelog'] = array(); 1552 } 1553 } else { 1554 if (isset($this->_oldPackageXml['release_notes'])) { 1555 $oldchangelog['release_notes'] = $this->_oldPackageXml['release_notes']; 1556 } 1557 if (isset($this->_oldPackageXml['version'])) { 1558 $oldchangelog['version'] = $this->_oldPackageXml['version']; 1559 } 1560 if (isset($this->_oldPackageXml['release_date'])) { 1561 $oldchangelog['release_date'] = $this->_oldPackageXml['release_date']; 1562 } 1563 if (isset($this->_oldPackageXml['release_license'])) { 1564 $oldchangelog['release_license'] = $this->_oldPackageXml['release_license']; 1565 } 1566 if (isset($this->_oldPackageXml['release_state'])) { 1567 $oldchangelog['release_state'] = $this->_oldPackageXml['release_state']; 1568 } 1569 } 1570 $hasoldversion = false; 1571 foreach ($this->_packageXml['changelog'] as $index => $changelog) { 1572 if ($oldchangelog && isset($oldchangelog['version']) 1573 && strnatcasecmp($oldchangelog['version'], $changelog['version']) == 0) { 1574 $hasoldversion = true; 1575 } 1576 if (isset($changelog['version']) && strnatcasecmp($changelog['version'], $this->_options['version']) == 0) { 1577 $curlog = $index; 1578 } 1579 if (isset($this->_packageXml['changelog'][$index]['release_notes'])) { 1580 $this->_packageXml['changelog'][$index]['release_notes'] = trim($changelog['release_notes']); 1581 } 1582 // the parsing of the release notes adds a \n for some reason 1583 } 1584 if (!$hasoldversion && $oldchangelog && count($oldchangelog) 1585 && $oldchangelog['version'] != $this->_options['version']) { 1586 $this->_packageXml['changelog'][] = $oldchangelog; 1587 } 1588 $notes = ($this->_options['changelognotes'] ? 1589 $this->_options['changelognotes'] : $this->_options['notes']); 1590 $changelog = array('version' => $this->_options['version'], 1591 'release_date' => date('Y-m-d'), 1592 'release_license' => $this->_options['license'], 1593 'release_state' => $this->_options['state'], 1594 'release_notes' => $notes, 1595 ); 1596 if ($curlog !== false) { 1597 $this->_packageXml['changelog'][$curlog] = $changelog; 1598 } else { 1599 $this->_packageXml['changelog'][] = $changelog; 1600 } 1601 usort($this->_packageXml['changelog'], array($this, '_changelogsort')); 1602 } 1603 1604 /** 1605 * User-defined comparison function to sort changelog array 1606 * 1607 * @return integer sort comparaison result (-1, 0, +1) of two elements $a and $b 1608 * @access private 1609 * @since 0.12 1610 */ 1611 function _changelogsort($a, $b) 1612 { 1613 if ($this->_options['changelogoldtonew']) { 1614 $c = strtotime($a['release_date']); 1615 $d = strtotime($b['release_date']); 1616 $v1 = $a['version']; 1617 $v2 = $b['version']; 1618 } else { 1619 $d = strtotime($a['release_date']); 1620 $c = strtotime($b['release_date']); 1621 $v2 = $a['version']; 1622 $v1 = $b['version']; 1623 } 1624 if ($c - $d > 0) { 1625 return 1; 1626 } elseif ($c - $d < 0) { 1627 return -1; 1628 } 1629 return version_compare($v1, $v2); 1630 } 1631 1632 /** 1633 * @param string $path full path to package file 1634 * @param string $packagefile (optional) name of package file 1635 * 1636 * @return true|PEAR_Error 1637 * @uses _generateNewPackageXML() if no package.xml is found, it 1638 * calls this to create a new one 1639 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS 1640 * @throws PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST 1641 * @access private 1642 * @since 0.1 1643 */ 1644 function _getExistingPackageXML($path, $packagefile = 'package.xml') 1645 { 1646 if (is_string($path) && is_dir($path)) { 1647 $contents = false; 1648 if (file_exists($path . $packagefile)) { 1649 $contents = file_get_contents($path . $packagefile); 1650 } 1651 if (!$contents) { 1652 return $this->_generateNewPackageXML(); 1653 } 1654 1655 $PEAR_Common = $this->_options['pearcommonclass']; 1656 if (!class_exists($PEAR_Common)) { 1657 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS); 1658 } 1659 1660 include_once 'PEAR/PackageFile.php'; 1661 $z = &PEAR_Config::singleton(); 1662 $pkg = new PEAR_PackageFile($z); 1663 $pf = &$pkg->fromXmlString($contents, PEAR_VALIDATE_DOWNLOADING, $path . $packagefile); 1664 if (PEAR::isError($pf)) { 1665 return $pf; 1666 } 1667 if ($pf->getPackagexmlVersion() != '1.0') { 1668 return PEAR::raiseError('PEAR_PackageFileManager can only manage ' . 1669 'package.xml version 1.0, use PEAR_PackageFileManager_v2 for newer' . 1670 ' package files'); 1671 } 1672 $this->_oldPackageXml = 1673 $this->_packageXml = $pf->toArray(); 1674 1675 if (PEAR::isError($this->_packageXml)) { 1676 return $this->_packageXml; 1677 } 1678 if ($this->_options['cleardependencies']) { 1679 $this->_packageXml['release_deps'] = $this->_options['deps']; 1680 } 1681 if ($this->_options['deps'] !== false) { 1682 $this->_packageXml['release_deps'] = $this->_options['deps']; 1683 } else { 1684 if (isset($this->_packageXml['release_deps'])) { 1685 $this->_options['deps'] = $this->_packageXml['release_deps']; 1686 } 1687 } 1688 if ($this->_options['maintainers'] !== false) { 1689 $this->_packageXml['maintainers'] = $this->_options['maintainers']; 1690 } else { 1691 $this->_options['maintainers'] = $this->_packageXml['maintainers']; 1692 } 1693 unset($this->_packageXml['filelist']); 1694 unset($this->_packageXml['provides']); 1695 return true; 1696 } else { 1697 if (!is_string($path)) { 1698 $path = gettype($path); 1699 } 1700 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST, 1701 $path); 1702 } 1703 } 1704 1705 /** 1706 * Create the structure for a new package.xml 1707 * 1708 * @uses $_packageXml emulates reading in a package.xml 1709 * by using the package, summary and description 1710 * options 1711 * @return true|PEAR_Error 1712 * @access private 1713 * @since 0.9 1714 */ 1715 function _generateNewPackageXML() 1716 { 1717 $this->_oldPackageXml = false; 1718 if (!isset($this->_options['package']) || empty($this->_options['package'])) { 1719 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPACKAGE); 1720 } 1721 if (!isset($this->_options['summary']) || empty($this->_options['summary'])) { 1722 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSUMMARY); 1723 } 1724 if (!isset($this->_options['description']) || empty($this->_options['description'])) { 1725 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NODESC); 1726 } 1727 $this->_packageXml = array(); 1728 $this->_packageXml['package'] = $this->_options['package']; 1729 $this->_packageXml['summary'] = $this->_options['summary']; 1730 $this->_packageXml['description'] = $this->_options['description']; 1731 $this->_packageXml['changelog'] = array(); 1732 if ($this->_options['deps'] !== false) { 1733 $this->_packageXml['release_deps'] = $this->_options['deps']; 1734 } else { 1735 $this->_packageXml['release_deps'] = $this->_options['deps'] = array(); 1736 } 1737 if ($this->_options['maintainers'] !== false) { 1738 $this->_packageXml['maintainers'] = $this->_options['maintainers']; 1739 } else { 1740 $this->_packageXml['maintainers'] = $this->_options['maintainers'] = array(); 1741 } 1742 return true; 1743 } 1744} 1745?> 1746