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