1 <?php
2 //
3 // +------------------------------------------------------------------------+
4 // | PEAR :: Package File Manager |
5 // +------------------------------------------------------------------------+
6 // | Copyright (c) 2003 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 //
20
21 /**
22 * @package PEAR_PackageFileManager
23 */
24 /**
25 * PEAR installer
26 */
27 require_once 'PEAR/Common.php';
28 /**#@+
29 * Error Codes
30 */
31 define('PEAR_PACKAGEFILEMANAGER_NOSTATE', 1);
32 define('PEAR_PACKAGEFILEMANAGER_NOVERSION', 2);
33 define('PEAR_PACKAGEFILEMANAGER_NOPKGDIR', 3);
34 define('PEAR_PACKAGEFILEMANAGER_NOBASEDIR', 3);
35 define('PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND', 4);
36 define('PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE', 5);
37 define('PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE', 6);
38 define('PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE', 7);
39 define('PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE', 8);
40 define('PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE', 9);
41 define('PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST', 10);
42 define('PEAR_PACKAGEFILEMANAGER_NOCVSENTRIES', 11);
43 define('PEAR_PACKAGEFILEMANAGER_DIR_DOESNT_EXIST', 12);
44 define('PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS', 13);
45 define('PEAR_PACKAGEFILEMANAGER_NOPACKAGE', 14);
46 define('PEAR_PACKAGEFILEMANAGER_WRONG_MROLE', 15);
47 define('PEAR_PACKAGEFILEMANAGER_NOSUMMARY', 16);
48 define('PEAR_PACKAGEFILEMANAGER_NODESC', 17);
49 define('PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS', 18);
50 define('PEAR_PACKAGEFILEMANAGER_NO_FILES', 19);
51 define('PEAR_PACKAGEFILEMANAGER_IGNORED_EVERYTHING', 20);
52 /**#@-*/
53 /**
54 * Error messages
55 * @global array $GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS']
56 */
57 $GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'] =
58 array(
59 'en' =>
60 array(
61 PEAR_PACKAGEFILEMANAGER_NOSTATE =>
62 'Release State (option \'state\') must by specified in PEAR_PackageFileManager setOptions (alpha|beta|stable)',
63 PEAR_PACKAGEFILEMANAGER_NOVERSION =>
64 'Release Version (option \'version\') must be specified in PEAR_PackageFileManager setOptions',
65 PEAR_PACKAGEFILEMANAGER_NOPKGDIR =>
66 'Package source base directory (option \'packagedirectory\') must be ' .
67 'specified in PEAR_PackageFileManager setOptions',
68 PEAR_PACKAGEFILEMANAGER_NOPKGDIR =>
69 'Package install base directory (option \'baseinstalldir\') must be ' .
70 'specified in PEAR_PackageFileManager setOptions',
71 PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND =>
72 'Base class "%s" can\'t be located',
73 PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE =>
74 'Base class "%s" can\'t be located in default or user-specified directories',
75 PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE =>
76 'Failed to write package.xml file to destination directory',
77 PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE =>
78 'Destination directory "%s" is unwritable',
79 PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE =>
80 'Failed to copy package.xml.tmp file to package.xml',
81 PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE =>
82 'Failed to open temporary file "%s" for writing',
83 PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST =>
84 'package.xml file path "%s" doesn\'t exist or isn\'t a directory',
85 PEAR_PACKAGEFILEMANAGER_NOCVSENTRIES =>
86 'Directory "%s" is not a CVS directory (it must have the CVS/Entries file)',
87 PEAR_PACKAGEFILEMANAGER_DIR_DOESNT_EXIST =>
88 'Package source base directory "%s" doesn\'t exist or isn\'t a directory',
89 PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS =>
90 'Run $managerclass->setOptions() before any other methods',
91 PEAR_PACKAGEFILEMANAGER_NOPACKAGE =>
92 'Package Name (option \'package\') must by specified in PEAR_PackageFileManager '.
93 'setOptions to create a new package.xml',
94 PEAR_PACKAGEFILEMANAGER_NOSUMMARY =>
95 'Package Summary (option \'summary\') must by specified in PEAR_PackageFileManager' .
96 ' setOptions to create a new package.xml',
97 PEAR_PACKAGEFILEMANAGER_NODESC =>
98 'Detailed Package Description (option \'description\') must be' .
99 ' specified in PEAR_PackageFileManager constructor to create a new package.xml',
100 PEAR_PACKAGEFILEMANAGER_WRONG_MROLE =>
101 'Maintainer role must be one of "%s", was "%s"',
102 PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS =>
103 'Add maintainers to a package before generating the package.xml',
104 PEAR_PACKAGEFILEMANAGER_NO_FILES =>
105 'No files found, check the path "%s"',
106 PEAR_PACKAGEFILEMANAGER_IGNORED_EVERYTHING =>
107 'No files left, check the path "%s" and ignore option "%s"',
108 ),
109 // other language translations go here
110 );
111 /**
112 * PEAR :: PackageGenerate updates the <filelist></filelist> section
113 * of a PEAR package.xml file to reflect the current files in
114 * preparation for a release.
115 *
116 * The PEAR_PackageGenerate class uses a plugin system to generate the
117 * list of files in a package. This allows both standard recursive
118 * directory parsing (plugin type file) and more intelligent options
119 * such as the CVS browser {@link PEAR_PackageFileManager_Cvs}, which
120 * grabs all files in a local CVS checkout to create the list, ignoring
121 * any other local files.
122 *
123 * Other options include specifying roles for file extensions (all .php
124 * files are role="php", for example), roles for directories (all directories
125 * named "tests" are given role="tests" by default), and exceptions.
126 * Exceptions are specific pathnames with * and ? wildcards that match
127 * a default role, but should have another. For example, perhaps
128 * a debug.tpl template would normally be data, but should be included
129 * in the docs role. Along these lines, to exclude files entirely,
130 * use the ignore option.
131 *
132 * Required options for a release include version, baseinstalldir, state,
133 * and packagedirectory (the full path to the local location of the
134 * package to create a package.xml file for)
135 *
136 * Example usage:
137 * <code>
138 * <?php
139 * require_once('PEAR/PackageFileManager.php');
140 * $packagexml = new PEAR_PackageFileManager;
141 * $e = $packagexml->setOptions(
142 * array('baseinstalldir' => 'PhpDocumentor',
143 * 'version' => '1.2.1',
144 * 'packagedirectory' => 'C:/Web Pages/chiara/phpdoc2/',
145 * 'state' => 'stable',
146 * 'filelistgenerator' => 'cvs', // generate from cvs, use file for directory
147 * 'notes' => 'We\'ve implemented many new and exciting features',
148 * 'ignore' => array('TODO', 'tests/'), // ignore TODO, all files in tests/
149 * 'installexceptions' => array('phpdoc' => '/*'), // baseinstalldir ="/" for phpdoc
150 * 'dir_roles' => array('tutorials' => 'doc'),
151 * 'exceptions' => array('README' => 'doc', // README would be data, now is doc
152 * 'PHPLICENSE.txt' => 'doc'))); // same for the license
153 * if (PEAR::isError($e)) {
154 * echo $e->getMessage();
155 * die();
156 * }
157 * $packagexml->addRole('pkg', 'doc'); // add a new role mapping
158 * $e = $packagexml->writePackageFile();
159 * if (PEAR::isError($e)) {
160 * echo $e->getMessage();
161 * die();
162 * }
163 * ?>
164 *
165 * In addition, a package.xml file can now be generated from
166 * scratch, with the usage of new options package, summary, description, and
167 * the use of the {@link addMaintainer()} method
168 * </code>
169 * @package PEAR_PackageFileManager
170 * @todo add support for <replace> tag in file
171 * @todo add support for md5sum attribute
172 * @todo add support for platform attribute
173 */
174 class PEAR_PackageFileManager
175 {
176 /**
177 * Format: array(array(regexp-ready string to search for whole path,
178 * regexp-ready string to search for basename of ignore strings),...)
179 * @var false|array
180 * @access private
181 */
182 var $_ignore = false;
183
184 /**
185 * Contents of the package.xml file
186 * @var string
187 * @access private
188 */
189 var $_packageXml = false;
190
191 /**
192 * Contents of the original package.xml file, if any
193 * @var string
194 * @access private
195 */
196 var $_oldPackageXml = false;
197
198 /**
199 * @access private
200 * @var PEAR_Common
201 */
202 var $_pear;
203
204 /**
205 * @access private
206 * @var string
207 */
208 var $_options = array(
209 'packagefile' => 'package.xml',
210 'doctype' => 'http://pear.php.net/dtd/package-1.0',
211 'filelistgenerator' => 'file',
212 'license' => 'PHP License',
213 'roles' =>
214 array(
215 'php' => 'php',
216 'html' => 'doc',
217 '*' => 'data',
218 ),
219 'dir_roles' =>
220 array(
221 'docs' => 'doc',
222 'examples' => 'doc',
223 'tests' => 'tests',
224 ),
225 'exceptions' => array(),
226 'installexceptions' => array(),
227 'ignore' => array(),
228 'deps' => false,
229 'maintainers' => false,
230 'notes' => '',
231 'changelognotes' => false,
232 'outputdirectory' => false,
233 'pathtopackagefile' => false,
234 'lang' => 'en',
235 'configure_options' => array(),
236 );
237
238 /**
239 * Does nothing, use setOptions
240 * @see setOptions
241 */
242 function PEAR_PackageFileManager()
243 {
244 }
245
246 /**
247 * Set package.xml generation options
248 *
249 * The options array is indexed as follows:
250 * <code>
251 * $options = array('option_name' => <optionvalue>);
252 * </code>
253 *
254 * The documentation below simplifies this description through
255 * the use of option_name without quotes
256 *
257 * Configuration options:
258 * - lang: lang controls the language in which error messages are
259 * displayed. There are currently only English error messages,
260 * but any contributed will be added over time.<br />
261 * Possible values: en (default)
262 * - packagefile: the name of the packagefile, defaults to package.xml
263 * - pathtopackagefile: the path to an existing package file to read in,
264 * if different from the packagedirectory
265 * - packagedirectory: the path to the base directory of the package. For
266 * package PEAR_PackageFileManager, this path is
267 * /path/to/pearcvs/pear/PEAR_PackageFileManager where
268 * /path/to/pearcvs is a local path on your hard drive
269 * - outputdirectory: the path in which to place the generated package.xml
270 * by default, this is ignored, and the package.xml is
271 * created in the packagedirectory
272 * - filelistgenerator: the <filelist> section plugin which will be used.
273 * In this release, there are two generator plugins,
274 * file and cvs. For details, see the docs for these
275 * plugins
276 * - usergeneratordir: For advanced users. If you write your own filelist
277 * generator plugin, use this option to tell
278 * PEAR_PackageFileManager where to find the file that
279 * contains it. If the plugin is named foo, the class
280 * must be named PEAR_PackageFileManager_Foo
281 * no matter where it is located. By default, the Foo
282 * plugin is located in PEAR/PackageFileManager/Foo.php.
283 * If you pass /path/to/foo in this option, setOptions
284 * will look for PEAR_PackageFileManager_Foo in
285 * /path/to/foo/Foo.php
286 * - doctype: Specifies the DTD of the package.xml file. Default is
287 * http://pear.php.net/dtd/package-1.0
288 *
289 * package.xml simple options:
290 * - baseinstalldir: The base directory to install this package in. For
291 * package PEAR_PackageFileManager, this is "PEAR", for
292 * package PEAR, this is "/"
293 * - license: The license this release is released under. Default is
294 * PHP License if left unspecified
295 * - notes: Release notes, any text describing what makes this release unique
296 * - changelognotes: notes for the changelog, this should be more detailed than
297 * the release notes. By default, PEAR_PackageFileManager uses
298 * the notes option for the changelog as well
299 * - version: The version number for this release. Remember the convention for
300 * numbering: initial alpha is between 0 and 1, add b<beta number> for
301 * beta as in 1.0b1, the integer portion of the version should specify
302 * backwards compatibility, as in 1.1 is backwards compatible with 1.0,
303 * but 2.0 is not backwards compatible with 1.10. Also note that 1.10
304 * is a greater release version than 1.1 (think of it as "one point ten"
305 * and "one point one"). Bugfix releases should be a third decimal as in
306 * 1.0.1, 1.0.2
307 * - package: [optional] Package name. Use this to create a new package.xml, or
308 * overwrite an existing one from another package used as a template
309 * - summary: [optional] Summary of package purpose
310 * - description: [optional] Description of package purpose. Note that the above
311 * three options are not optional when creating a new package.xml
312 * from scratch
313 *
314 * package.xml complex options:
315 * - roles: this is an array mapping file extension to install role. This
316 * specifies default behavior that can be overridden by the exceptions
317 * option and dir_roles option. use {@link addRole()} to add a new
318 * role to the pre-existing array
319 * - dir_roles: this is an array mapping directory name to install role. All
320 * files in a directory whose name matches the directory will be
321 * given the install role specified. Single files can be excluded
322 * from this using the exceptions option. The directory should be
323 * a relative path from the baseinstalldir, or "/" for the baseinstalldir
324 * - exceptions: specify file role for specific files. This array maps all files
325 * matching the exact name of a file to a role as in "file.ext" => "role"
326 * - installexceptions: array mapping of specific filenames to baseinstalldir values. Use
327 * this to force the installation of a file into another directory,
328 * such as forcing a script to be in the root scripts directory so that
329 * it will be in the path
330 * - deps: dependency array. Pass in an empty array to clear all dependencies, and use
331 * {@link addDependency()} to add new ones/replace existing ones
332 * - maintainers: maintainers array. Pass in an empty array to clear all maintainers, and
333 * use {@link addMaintainer()} to add a new maintainer/replace existing maintainer
334 * - configure_options: array specifies build options for PECL packages (you should probably
335 * use PECL_Gen instead, but it's here for completeness)
336 * - ignore: an array of filenames, directory names, or wildcard expressions specifying
337 * files to exclude entirely from the package.xml. Wildcards are operating system
338 * wildcards * and ?. file*foo.php will exclude filefoo.php, fileabrfoo.php and
339 * filewho_is_thisfoo.php. file?foo.php will exclude fileafoo.php and will not
340 * exclude fileaafoo.php. test/ will exclude all directories and subdirectories of
341 * ANY directory named test encountered in directory parsing. *test* will exclude
342 * all files and directories that contain test in their name
343 * @see PEAR_PackageFileManager_Generator_File
344 * @see PEAR_PackageFileManager_Generator_CVS
345 * @return void|PEAR_Error
346 * @throws PEAR_PACKAGEFILEMANAGER_NOSTATE
347 * @throws PEAR_PACKAGEFILEMANAGER_NOVERSION
348 * @throws PEAR_PACKAGEFILEMANAGER_NOPKGDIR
349 * @throws PEAR_PACKAGEFILEMANAGER_NOBASEDIR
350 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE
351 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND
352 * @param array
353 */
354 function setOptions($options = array())
355 {
356 if (!isset($options['state'])) {
357 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSTATE);
358 }
359 if (!isset($options['version'])) {
360 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOVERSION);
361 }
362 if (!isset($options['packagedirectory'])) {
363 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPKGDIR);
364 } else {
365 $options['packagedirectory'] = str_replace(DIRECTORY_SEPARATOR,
366 '/',
367 realpath($options['packagedirectory']));
368 if ($options['packagedirectory']{strlen($options['packagedirectory']) - 1} != '/') {
369 $options['packagedirectory'] .= '/';
370 }
371 }
372 if (!isset($options['baseinstalldir'])) {
373 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOBASEDIR);
374 }
375 $this->_options = array_merge($this->_options, $options);
376
377 $path = ($this->_options['pathtopackagefile'] ?
378 $this->_options['pathtopackagefile'] : $this->_options['packagedirectory']);
379 $this->_options['filelistgenerator'] = ucfirst(strtolower($this->_options['filelistgenerator']));
380 if (PEAR::isError($res = $this->_getExistingPackageXML($path, $this->_options['packagefile']))) {
381 return $res;
382 }
383 // attempt to load the interface from the standard PEAR location
384 @include_once('PEAR/PackageFileManager/' . $this->_options['filelistgenerator'] . '.php');
385 if (!class_exists('PEAR_PackageFileManager_' . $this->_options['filelistgenerator'])) {
386 if (isset($this->_options['usergeneratordir'])) {
387 // attempt to load from a user-specified directory
388 $this->_options['usergeneratordir'] = str_replace(DIRECTORY_SEPARATOR,
389 '/',
390 realpath($this->_options['usergeneratordir']));
391 if ($this->_options['usergeneratordir']{strlen($this->_options['usergeneratordir']) - 1} != '/') {
392 $this->_options['usergeneratordir'] .= '/';
393 }
394 @include_once($this->_options['usergeneratordir'] . $this->_options['filelistgenerator'] . '.php');
395 if (!class_exists('PEAR_PackageFileManager_' . $this->_options['filelistgenerator'])) {
396 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE,
397 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator']);
398 }
399 } else {
400 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND,
401 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator']);
402 }
403 }
404 }
405
406 /**
407 * Add an extension/role mapping to the role mapping option
408 * @param string file extension
409 * @param string role
410 */
411 function addRole($extension, $role)
412 {
413 $this->_options['roles'][$extension] = $role;
414 }
415
416 /**
417 * Add a maintainer to the list of maintainers.
418 *
419 * Every maintainer must have a valid account at pear.php.net. The
420 * first parameter is the account name (for instance, cellog is the
421 * handle for Greg Beaver at pear.php.net). Every maintainer has
422 * one of four possible roles:
423 * - lead: the primary maintainer
424 * - developer: an important developer on the project
425 * - contributor: self-explanatory
426 * - helper: ditto
427 *
428 * Finally, specify the name and email of the maintainer
429 * @param string username on pear.php.net of maintainer
430 * @param lead|developer|contributor|helperrole of maintainer
431 * @param string full name of maintainer
432 * @param string email address of maintainer
433 */
434 function addMaintainer($handle, $role, $name, $email)
435 {
436 if (!$this->_packageXml) {
437 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
438 }
439 if (!in_array($role, $GLOBALS['_PEAR_Common_maintainer_roles'])) {
440 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_WRONG_MROLE,
441 implode(', ', $GLOBALS['_PEAR_Common_maintainer_roles']),
442 $role);
443 }
444 if (!isset($this->_packageXml['maintainers'])) {
445 $this->_packageXml['maintainers'] = array();
446 }
447 $found = false;
448 foreach($this->_packageXml['maintainers'] as $index => $maintainer) {
449 if ($maintainer['handle'] == $handle) {
450 $found = $index;
451 break;
452 }
453 }
454 $maintainer =
455 array('handle' => $handle, 'role' => $role, 'name' => $name, 'email' => $email);
456 if ($found !== false) {
457 $this->_packageXml['maintainers'][$found] = $maintainer;
458 } else {
459 $this->_packageXml['maintainers'][] = $maintainer;
460 }
461 }
462
463 /**
464 * Add an install-time configuration option for building of source
465 *
466 * This option is only useful to PECL projects that are built upon
467 * installation
468 * @param string name of the option
469 * @param string prompt to display to the user
470 * @param string default value
471 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
472 * @return void|PEAR_Error
473 */
474 function addConfigureOption($name, $prompt, $default = null)
475 {
476 if (!$this->_packageXml) {
477 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
478 }
479 if (!isset($this->_packageXml['configure_options'])) {
480 $this->_packageXml['configure_options'] = array();
481 }
482 $found = false;
483 foreach($this->_packageXml['configure_options'] as $index => $option) {
484 if ($option['name'] == $name) {
485 $found = $index;
486 break;
487 }
488 }
489 $option = array('name' => $name, 'prompt' => $prompt);
490 if (isset($default)) {
491 $option['default'] = $default;
492 }
493 if ($found !== false) {
494 $this->_packageXml['configure_options'][$found] = $option;
495 } else {
496 $this->_packageXml['configure_options'][] = $option;
497 }
498 }
499
500 /**
501 * Add a dependency on another package, or an extension/php
502 *
503 * This will overwrite an existing dependency if it is found. In
504 * other words, if a dependency on PHP 4.1.0 exists, and
505 * addDependency('php', '4.3.0', 'ge', 'php') is called, the existing
506 * dependency on PHP 4.1.0 will be overwritten with the new one on PHP 4.3.0
507 * @param string Dependency element name
508 * @param string Dependency version
509 * @param string A specific operator for the version, this can be one of:
510 * 'has', 'not', 'lt', 'le', 'eq', 'ne', 'ge', or 'gt'
511 * @param string Dependency type. This can be one of:
512 * 'pkg', 'ext', 'php', 'prog', 'os', 'sapi', or 'zend'
513 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
514 * @return void|PEAR_Error
515 */
516 function addDependency($name, $version = false, $operator = 'ge', $type = 'pkg')
517 {
518 if (!$this->_packageXml) {
519 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
520 }
521 if (!isset($this->_packageXml['release_deps'])) {
522 $this->_packageXml['release_deps'] = array();
523 }
524 $found = false;
525 foreach($this->_packageXml['release_deps'] as $index => $dep) {
526 if ($type == 'php') {
527 if ($dep['type'] == 'php') {
528 $found = $index;
529 break;
530 }
531 } else {
532 if (isset($dep['name']) && $dep['name'] == $name && $dep['type'] == $type) {
533 $found = $index;
534 break;
535 }
536 }
537 }
538 $dep =
539 array(
540 'name' => $name,
541 'type' => $type);
542 if ($type == 'php') {
543 unset($dep['name']);
544 }
545 if ($version) {
546 $dep['version'] = $version;
547 if ($operator) {
548 $dep['rel'] = $operator;
549 }
550 }
551
552 if ($found !== false) {
553 $this->_packageXml['release_deps'][$found] = $dep; // overwrite existing dependency
554 } else {
555 $this->_packageXml['release_deps'][] = $dep; // add new dependency
556 }
557 }
558
559 /**
560 * Writes the package.xml file out with the newly created <release></release> tag
561 *
562 * ALWAYS use {@link debugPackageFile} to verify that output is correct before
563 * overwriting your package.xml
564 * @param boolean null if no debugging, true if web interface, false if command-line
565 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
566 * @throws PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS
567 * @throws PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE
568 * @throws PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE
569 * @throws PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE
570 * @throws PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE
571 * @return void|PEAR_Error
572 */
573 function writePackageFile($debuginterface = null)
574 {
575 if (!$this->_packageXml) {
576 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
577 }
578 if (!isset($this->_packageXml['maintainers']) || empty($this->_packageXml['maintainers'])) {
579 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS);
580 }
581 extract($this->_options);
582 $date = date('Y-m-d');
583 if (isset($package)) {
584 $this->_packageXml['package'] = $package;
585 }
586 if (isset($summary)) {
587 $this->_packageXml['summary'] = $summary;
588 }
589 if (isset($description)) {
590 $this->_packageXml['description'] = $description;
591 }
592 $this->_packageXml['release_date'] = $date;
593 $this->_packageXml['version'] = $version;
594 $this->_packageXml['release_license'] = $license;
595 $this->_packageXml['release_state'] = $state;
596 $this->_packageXml['release_notes'] = $notes;
597 $this->_pear = new PEAR_Common;
598 $this->_packageXml['filelist'] = $this->_getFileList();
599 if (PEAR::isError($this->_packageXml['filelist'])) {
600 return $this->_packageXml['filelist'];
601 }
602 if (isset($this->_pear->pkginfo['provides'])) {
603 $this->_packageXml['provides'] = $this->_pear->pkginfo['provides'];
604 }
605 $this->_packageXml['release_deps'] = $this->_getDependencies();
606 $this->_updateChangeLog();
607 $common = &$this->_pear;
608 $packagexml = $common->xmlFromInfo($this->_packageXml);
609 if (!strpos($packagexml, '<!DOCTYPE')) {
610 // hack to fix pear
611 $packagexml = str_replace('<package version="1.0">',
612 '<!DOCTYPE package SYSTEM "' . $this->_options['doctype'] .
613 "\">\n<package version=\"1.0\">",
614 $packagexml);
615 }
616 if (isset($debuginterface)) {
617 if ($debuginterface) {
618 echo '<pre>' . htmlentities($packagexml) . '</pre>';
619 } else {
620 echo $packagexml;
621 }
622 return true;
623 }
624 $outputdir = ($this->_options['outputdirectory'] ?
625 $this->_options['outputdirectory'] : $this->_options['packagedirectory']);
626 if ((file_exists($outputdir . $this->_options['packagefile']) &&
627 is_writable($outputdir . $this->_options['packagefile']))
628 ||
629 @touch($outputdir . $this->_options['packagefile'])) {
630 if ($fp = @fopen($outputdir . $this->_options['packagefile'] . '.tmp', "w")) {
631 $written = @fwrite($fp, $packagexml);
632 @fclose($fp);
633 if ($written === false) {
634 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE);
635 }
636 if (!@copy($outputdir . $this->_options['packagefile'] . '.tmp',
637 $outputdir . $this->_options['packagefile'])) {
638 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE);
639 } else {
640 @unlink($outputdir . $this->_options['packagefile'] . '.tmp');
641 return true;
642 }
643 } else {
644 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE,
645 $outputdir . $this->_options['packagefile'] . '.tmp');
646 }
647 } else {
648 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE, $outputdir);
649 }
650 }
651
652 /**
653 * ALWAYS use this to test output before overwriting your package.xml!!
654 * @uses writePackageFile() calls with the debug parameter set based on
655 * whether it is called from the command-line or web interface
656 */
657 function debugPackageFile()
658 {
659 $webinterface = isset($_SERVER['PATH_TRANSLATED']);
660 return $this->writePackageFile($webinterface);
661 }
662
663 /**
664 * Utility function to shorten error generation code
665 *
666 * {@source }
667 * @return PEAR_Error
668 * @static
669 */
670 function raiseError($code, $i1 = '', $i2 = '')
671 {
672 return PEAR::raiseError('PEAR_PackageFileManager Error: ' .
673 sprintf($GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'][$this->_options['lang']][$code],
674 $i1, $i2));
675 }
676
677 /**
678 * Uses {@link PEAR_Common::analyzeSourceCode()} and {@link PEAR_Common::buildProvidesArray()}
679 * to create the <provides></provides> section of the package.xml
680 * @param PEAR_Common
681 * @param string path to source file
682 * @access private
683 */
684 function _addProvides(&$pear, $file)
685 {
686 if (!($a = $pear->analyzeSourceCode($file))) {
687 return;
688 } else {
689 $pear->buildProvidesArray($a);
690 }
691 }
692
693 /**
694 * @uses getDirTag() generate the xml from the array
695 * @return string
696 * @access private
697 */
698 function _getFileList()
699 {
700 $generatorclass = 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator'];
701 $generator = new $generatorclass($this, $this->_options);
702 return $this->_getDirTag($generator->getFileList());
703 }
704
705 /**
706 * Recursively generate the <filelist> section's <dir> and <file> tags
707 * @param array|PEAR_Errorthe sorted directory structure, or an error
708 * from filelist generation
709 * @param false|stringwhether the parent directory has a role this should
710 * inherit
711 * @param integer indentation level
712 * @return array|PEAR_Error
713 * @access private
714 */
715 function _getDirTag($struc, $role=false)
716 {
717 if (PEAR::isError($struc)) {
718 return $struc;
719 }
720 extract($this->_options);
721 $ret = array();
722 foreach($struc as $dir => $files) {
723 if ($dir === '/') {
724 return $this->_getDirTag($struc[$dir], $role);
725 } else {
726 if (!isset($files['file'])) {
727 $myrole = '';
728 if ($role) {
729 $myrole = $role;
730 } elseif (isset($dir_roles[$dir])) {
731 $myrole = $dir_roles[$dir];
732 }
733 $ret = array_merge($ret, $this->_getDirTag($files, $myrole));
734 } else {
735 $myrole = '';
736 if (!$role)
737 {
738 $myrole = false;
739 if (isset($exceptions[$files['file']])) {
740 $myrole = $exceptions[$files['file']];
741 } elseif (isset($roles[$files['ext']])) {
742 $myrole = $roles[$files['ext']];
743 } else {
744 $myrole = $roles['*'];
745 }
746 } else {
747 $myrole = $role;
748 }
749 if (isset($installexceptions[$files['file']])) {
750 $bi = $installexceptions[$files['file']];
751 } else {
752 $bi = $baseinstalldir;
753 }
754 $ret[$files['path']] = array('role' => $myrole, 'baseinstalldir' => $bi);
755 if ($myrole == 'php') {
756 $this->_addProvides($this->_pear, $files['fullpath']);
757 }
758 }
759 }
760 }
761 return $ret;
762 }
763
764 /**
765 * Retrieve the 'deps' option passed to the constructor
766 * @access private
767 * @return array
768 */
769 function _getDependencies()
770 {
771 if ($this->_packageXml['release_deps']) {
772 return $this->_packageXml['release_deps'];
773 } else {
774 return array();
775 }
776 }
777
778 /**
779 * Creates a changelog entry with the current release
780 * notes and dates, or overwrites a previous creation
781 * @access private
782 */
783 function _updateChangeLog()
784 {
785 $curlog = false;
786 if (!isset($this->_packageXml['changelog'])) {
787 $changelog = array();
788 if (isset($this->_oldPackageXml['release_notes'])) {
789 $changelog['release_notes'] = $this->_oldPackageXml['release_notes'];
790 }
791 if (isset($this->_oldPackageXml['version'])) {
792 $changelog['version'] = $this->_oldPackageXml['version'];
793 }
794 if (isset($this->_oldPackageXml['release_date'])) {
795 $changelog['release_date'] = $this->_oldPackageXml['release_date'];
796 }
797 if (isset($this->_oldPackageXml['release_license'])) {
798 $changelog['release_license'] = $this->_oldPackageXml['release_license'];
799 }
800 if (isset($this->_oldPackageXml['release_state'])) {
801 $changelog['release_state'] = $this->_oldPackageXml['release_state'];
802 }
803 $this->_packageXml['changelog'] = array($changelog);
804 }
805 foreach($this->_packageXml['changelog'] as $index => $changelog) {
806 if (isset($changelog['version']) && $changelog['version'] == $this->_options['version']) {
807 $curlog = $index;
808 }
809 if (isset($this->_packageXml['changelog'][$index]['release_notes'])) {
810 $this->_packageXml['changelog'][$index]['release_notes'] = trim($changelog['release_notes']);
811 }
812 // the parsing of the release notes adds a \n for some reason
813 }
814 $notes = ($this->_options['changelognotes'] ?
815 $this->_options['changelognotes'] : $this->_options['notes']);
816 $changelog = array('version' => $this->_options['version'],
817 'release_date' => date('Y-m-d'),
818 'release_license' => $this->_options['license'],
819 'release_state' => $this->_options['state'],
820 'release_notes' => $notes,
821 );
822 if ($curlog !== false) {
823 $this->_packageXml['changelog'][$curlog] = $changelog;
824 } else {
825 $this->_packageXml['changelog'][] = $changelog;
826 }
827 }
828
829 /**
830 * @return true|PEAR_Error
831 * @uses _generateNewPackageXML() if no package.xml is found, it
832 * calls this to create a new one
833 * @param string full path to package file
834 * @param string name of package file
835 * @throws PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST
836 * @access private
837 */
838 function _getExistingPackageXML($path, $packagefile = 'package.xml')
839 {
840 if (@is_dir($path)) {
841 $contents = @file_get_contents($path . $packagefile);
842 if (!$contents) {
843 return $this->_generateNewPackageXML();
844 } else {
845 $common = new PEAR_Common;
846 $this->_oldPackageXml =
847 $this->_packageXml = $common->infoFromString($contents);
848 if (PEAR::isError($this->_packageXml)) {
849 return $this->_packageXml;
850 }
851 if ($this->_options['deps'] !== false) {
852 $this->_packageXml['release_deps'] = $this->_options['deps'];
853 } else {
854 $this->_options['deps'] = $this->_packageXml['release_deps'];
855 }
856 if ($this->_options['maintainers'] !== false) {
857 $this->_packageXml['maintainers'] = $this->_options['maintainers'];
858 } else {
859 $this->_options['maintainers'] = $this->_packageXml['maintainers'];
860 }
861 unset($this->_packageXml['filelist']);
862 unset($this->_packageXml['provides']);
863 }
864 return true;
865 } else {
866 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST,
867 $path);
868 }
869 }
870
871 /**
872 * Create the structure for a new package.xml
873 *
874 * @uses $_packageXml emulates reading in a package.xml
875 * by using the package, summary and description
876 * options
877 * @return true|PEAR_Error
878 * @access private
879 */
880 function _generateNewPackageXML()
881 {
882 $this->_oldPackageXml = false;
883 if (!isset($this->_options['package'])) {
884 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPACKAGE);
885 }
886 if (!isset($this->_options['summary'])) {
887 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSUMMARY);
888 }
889 if (!isset($this->_options['description'])) {
890 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NODESC);
891 }
892 $this->_packageXml = array();
893 $this->_packageXml['package'] = $this->_options['package'];
894 $this->_packageXml['summary'] = $this->_options['summary'];
895 $this->_packageXml['description'] = $this->_options['description'];
896 $this->_packageXml['changelog'] = array();
897 if ($this->_options['deps'] !== false) {
898 $this->_packageXml['release_deps'] = $this->_options['deps'];
899 } else {
900 $this->_packageXml['release_deps'] = $this->_options['deps'] = array();
901 }
902 if ($this->_options['maintainers'] !== false) {
903 $this->_packageXml['maintainers'] = $this->_options['maintainers'];
904 } else {
905 $this->_packageXml['maintainers'] = $this->_options['maintainers'] = array();
906 }
907 return true;
908 }
909 }
910
if (!function_exists('file_get_contents')) {
911 /**
912 * @ignore
913 */
914 function file_get_contents($path, $use_include_path = null, $context = null)
915 {
916 $a = @file($path, $use_include_path, $context);
917 if (is_array($a)) {
918 return implode('', $a);
919 } else {
920 return false;
921 }
922 }
923 }
924 ?>