Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

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