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 */
171 class PEAR_PackageFileManager
172 {
173 /**
174 * Format: array(array(regexp-ready string to search for whole path,
175 * regexp-ready string to search for basename of ignore strings),...)
176 * @var false|array
177 * @access private
178 */
179 var $_ignore = false;
180
181 /**
182 * Contents of the package.xml file
183 * @var string
184 * @access private
185 */
186 var $_packageXml = false;
187
188 /**
189 * @access private
190 * @var PEAR_Common
191 */
192 var $_pear;
193
194 /**
195 * @access private
196 * @var string
197 */
198 var $_options = array(
199 'packagefile' => 'package.xml',
200 'filelistgenerator' => 'file',
201 'license' => 'PHP License',
202 'roles' =>
203 array(
204 'php' => 'php',
205 'html' => 'doc',
206 '*' => 'data',
207 ),
208 'dir_roles' =>
209 array(
210 'docs' => 'doc',
211 'examples' => 'doc',
212 'tests' => 'tests',
213 ),
214 'exceptions' => array(),
215 'installexceptions' => array(),
216 'ignore' => array(),
217 'deps' => false,
218 'maintainers' => false,
219 'notes' => '',
220 'changelognotes' => false,
221 'outputdirectory' => false,
222 'pathtopackagefile' => false,
223 'lang' => 'en',
224 'configure_options' => array(),
225 );
226
227 /**
228 * Does nothing, use setOptions
229 * @see setOptions
230 */
231 function PEAR_PackageFileManager()
232 {
233 }
234
235 /**
236 * Set package.xml generation options
237 *
238 * The options array is indexed as follows:
239 * <code>
240 * $options = array('option_name' => <optionvalue>);
241 * </code>
242 *
243 * The documentation below simplifies this description through
244 * the use of option_name without quotes
245 *
246 * Configuration options:
247 * - lang: lang controls the language in which error messages are
248 * displayed. There are currently only English error messages,
249 * but any contributed will be added over time.<br />
250 * Possible values: en (default)
251 * - packagefile: the name of the packagefile, defaults to package.xml
252 * - pathtopackagefile: the path to an existing package file to read in,
253 * if different from the packagedirectory
254 * - packagedirectory: the path to the base directory of the package. For
255 * package PEAR_PackageFileManager, this path is
256 * /path/to/pearcvs/pear/PEAR_PackageFileManager where
257 * /path/to/pearcvs is a local path on your hard drive
258 * - outputdirectory: the path in which to place the generated package.xml
259 * by default, this is ignored, and the package.xml is
260 * created in the packagedirectory
261 * - filelistgenerator: the <filelist> section plugin which will be used.
262 * In this release, there are two generator plugins,
263 * file and cvs. For details, see the docs for these
264 * plugins
265 * - usergeneratordir: For advanced users. If you write your own filelist
266 * generator plugin, use this option to tell
267 * PEAR_PackageFileManager where to find the file that
268 * contains it. If the plugin is named foo, the class
269 * must be named PEAR_PackageFileManager_Foo
270 * no matter where it is located. By default, the Foo
271 * plugin is located in PEAR/PackageFileManager/Foo.php.
272 * If you pass /path/to/foo in this option, setOptions
273 * will look for PEAR_PackageFileManager_Foo in
274 * /path/to/foo/Foo.php
275 *
276 * package.xml simple options:
277 * - baseinstalldir: The base directory to install this package in. For
278 * package PEAR_PackageFileManager, this is "PEAR", for
279 * package PEAR, this is "/"
280 * - license: The license this release is released under. Default is
281 * PHP License if left unspecified
282 * - notes: Release notes, any text describing what makes this release unique
283 * - changelognotes: notes for the changelog, this should be more detailed than
284 * the release notes. By default, PEAR_PackageFileManager uses
285 * the notes option for the changelog as well
286 * - version: The version number for this release. Remember the convention for
287 * numbering: initial alpha is between 0 and 1, add b<beta number> for
288 * beta as in 1.0b1, the integer portion of the version should specify
289 * backwards compatibility, as in 1.1 is backwards compatible with 1.0,
290 * but 2.0 is not backwards compatible with 1.10. Also note that 1.10
291 * is a greater release version than 1.1 (think of it as "one point ten"
292 * and "one point one"). Bugfix releases should be a third decimal as in
293 * 1.0.1, 1.0.2
294 * - package: [optional] Package name. Use this to create a new package.xml, or
295 * overwrite an existing one from another package used as a template
296 * - summary: [optional] Summary of package purpose
297 * - description: [optional] Description of package purpose. Note that the above
298 * three options are not optional when creating a new package.xml
299 * from scratch
300 *
301 * package.xml complex options:
302 * - roles: this is an array mapping file extension to install role. This
303 * specifies default behavior that can be overridden by the exceptions
304 * option and dir_roles option. use {@link addRole()} to add a new
305 * role to the pre-existing array
306 * - dir_roles: this is an array mapping directory name to install role. All
307 * files in a directory whose name matches the directory will be
308 * given the install role specified. Single files can be excluded
309 * from this using the exceptions option. The directory should be
310 * a relative path from the baseinstalldir, or "/" for the baseinstalldir
311 * - exceptions: specify file role for specific files. This array maps all files
312 * matching the exact name of a file to a role as in "file.ext" => "role"
313 * - installexceptions: array mapping of specific filenames to baseinstalldir values. Use
314 * this to force the installation of a file into another directory,
315 * such as forcing a script to be in the root scripts directory so that
316 * it will be in the path
317 * - deps: dependency array. Pass in an empty array to clear all dependencies, and use
318 * {@link addDependency()} to add new ones/replace existing ones
319 * - maintainers: maintainers array. Pass in an empty array to clear all maintainers, and
320 * use {@link addMaintainer()} to add a new maintainer/replace existing maintainer
321 * - configure_options: array specifies build options for PECL packages (you should probably
322 * use PECL_Gen instead, but it's here for completeness)
323 * - ignore: an array of filenames, directory names, or wildcard expressions specifying
324 * files to exclude entirely from the package.xml. Wildcards are operating system
325 * wildcards * and ?. file*foo.php will exclude filefoo.php, fileabrfoo.php and
326 * filewho_is_thisfoo.php. file?foo.php will exclude fileafoo.php and will not
327 * exclude fileaafoo.php. test/ will exclude all directories and subdirectories of
328 * ANY directory named test encountered in directory parsing. *test* will exclude
329 * all files and directories that contain test in their name
330 * @see PEAR_PackageFileManager_Generator_File
331 * @see PEAR_PackageFileManager_Generator_CVS
332 * @return void|PEAR_Error
333 * @throws PEAR_PACKAGEFILEMANAGER_NOSTATE
334 * @throws PEAR_PACKAGEFILEMANAGER_NOVERSION
335 * @throws PEAR_PACKAGEFILEMANAGER_NOPKGDIR
336 * @throws PEAR_PACKAGEFILEMANAGER_NOBASEDIR
337 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE
338 * @throws PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND
339 * @param array
340 */
341 function setOptions($options = array())
342 {
343 if (!isset($options['state'])) {
344 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSTATE);
345 }
346 if (!isset($options['version'])) {
347 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOVERSION);
348 }
349 if (!isset($options['packagedirectory'])) {
350 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPKGDIR);
351 } else {
352 $options['packagedirectory'] = str_replace(DIRECTORY_SEPARATOR,
353 '/',
354 realpath($options['packagedirectory']));
355 if ($options['packagedirectory']{strlen($options['packagedirectory']) - 1} != '/') {
356 $options['packagedirectory'] .= '/';
357 }
358 }
359 if (!isset($options['baseinstalldir'])) {
360 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOBASEDIR);
361 }
362 $this->_options = array_merge($this->_options, $options);
363
364 $path = ($this->_options['pathtopackagefile'] ?
365 $this->_options['pathtopackagefile'] : $this->_options['packagedirectory']);
366 $this->_options['filelistgenerator'] = ucfirst(strtolower($this->_options['filelistgenerator']));
367 if (PEAR::isError($res = $this->_getExistingPackageXML($path, $this->_options['packagefile']))) {
368 return $res;
369 }
370 // attempt to load the interface from the standard PEAR location
371 @include_once('PEAR/PackageFileManager/' . $this->_options['filelistgenerator'] . '.php');
372 if (!class_exists('PEAR_PackageFileManager_' . $this->_options['filelistgenerator'])) {
373 if (isset($this->_options['usergeneratordir'])) {
374 // attempt to load from a user-specified directory
375 $this->_options['usergeneratordir'] = str_replace(DIRECTORY_SEPARATOR,
376 '/',
377 realpath($this->_options['usergeneratordir']));
378 if ($this->_options['usergeneratordir']{strlen($this->_options['usergeneratordir']) - 1} != '/') {
379 $this->_options['usergeneratordir'] .= '/';
380 }
381 @include_once($this->_options['usergeneratordir'] . $this->_options['filelistgenerator'] . '.php');
382 if (!class_exists('PEAR_PackageFileManager_' . $this->_options['filelistgenerator'])) {
383 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND_ANYWHERE,
384 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator']);
385 }
386 } else {
387 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_GENERATOR_NOTFOUND,
388 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator']);
389 }
390 }
391 }
392
393 /**
394 * Add an extension/role mapping to the role mapping option
395 * @param string file extension
396 * @param string role
397 */
398 function addRole($extension, $role)
399 {
400 $this->_options['roles'][$extension] = $role;
401 }
402
403 /**
404 * Add a maintainer to the list of maintainers.
405 *
406 * Every maintainer must have a valid account at pear.php.net. The
407 * first parameter is the account name (for instance, cellog is the
408 * handle for Greg Beaver at pear.php.net). Every maintainer has
409 * one of four possible roles:
410 * - lead: the primary maintainer
411 * - developer: an important developer on the project
412 * - contributor: self-explanatory
413 * - helper: ditto
414 *
415 * Finally, specify the name and email of the maintainer
416 * @param string username on pear.php.net of maintainer
417 * @param lead|developer|contributor|helperrole of maintainer
418 * @param string full name of maintainer
419 * @param string email address of maintainer
420 */
421 function addMaintainer($handle, $role, $name, $email)
422 {
423 if (!$this->_packageXml) {
424 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
425 }
426 if (!in_array($role, $GLOBALS['_PEAR_Common_maintainer_roles'])) {
427 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_WRONG_MROLE,
428 implode(', ', $GLOBALS['_PEAR_Common_maintainer_roles']),
429 $role);
430 }
431 if (!isset($this->_packageXml['maintainers'])) {
432 $this->_packageXml['maintainers'] = array();
433 }
434 $found = false;
435 foreach($this->_packageXml['maintainers'] as $index => $maintainer) {
436 if ($maintainer['handle'] == $handle) {
437 $found = $index;
438 break;
439 }
440 }
441 $maintainer =
442 array('handle' => $handle, 'role' => $role, 'name' => $name, 'email' => $email);
443 if ($found !== false) {
444 $this->_packageXml['maintainers'][$found] = $maintainer;
445 } else {
446 $this->_packageXml['maintainers'][] = $maintainer;
447 }
448 }
449
450 /**
451 * Add an install-time configuration option for building of source
452 *
453 * This option is only useful to PECL projects that are built upon
454 * installation
455 * @param string name of the option
456 * @param string prompt to display to the user
457 * @param string default value
458 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
459 * @return void|PEAR_Error
460 */
461 function addConfigureOption($name, $prompt, $default = null)
462 {
463 if (!$this->_packageXml) {
464 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
465 }
466 if (!isset($this->_packageXml['configure_options'])) {
467 $this->_packageXml['configure_options'] = array();
468 }
469 $found = false;
470 foreach($this->_packageXml['configure_options'] as $index => $option) {
471 if ($option['name'] == $name) {
472 $found = $index;
473 break;
474 }
475 }
476 $option = array('name' => $name, 'prompt' => $prompt);
477 if (isset($default)) {
478 $option['default'] = $default;
479 }
480 if ($found !== false) {
481 $this->_packageXml['configure_options'][$found] = $option;
482 } else {
483 $this->_packageXml['configure_options'][] = $option;
484 }
485 }
486
487 /**
488 * Add a dependency on another package, or an extension/php
489 *
490 * This will overwrite an existing dependency if it is found. In
491 * other words, if a dependency on PHP 4.1.0 exists, and
492 * addDependency('php', '4.3.0', 'ge', 'php') is called, the existing
493 * dependency on PHP 4.1.0 will be overwritten with the new one on PHP 4.3.0
494 * @param string Dependency element name
495 * @param string Dependency version
496 * @param string A specific operator for the version, this can be one of:
497 * 'has', 'not', 'lt', 'le', 'eq', 'ne', 'ge', or 'gt'
498 * @param string Dependency type. This can be one of:
499 * 'pkg', 'ext', 'php', 'prog', 'os', 'sapi', or 'zend'
500 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
501 * @return void|PEAR_Error
502 */
503 function addDependency($name, $version = false, $operator = 'ge', $type = 'pkg')
504 {
505 if (!$this->_packageXml) {
506 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
507 }
508 if (!isset($this->_packageXml['release_deps'])) {
509 $this->_packageXml['release_deps'] = array();
510 }
511 $found = false;
512 foreach($this->_packageXml['release_deps'] as $index => $dep) {
513 if ($dep['name'] == $name && $dep['type'] == $type) {
514 $found = $index;
515 break;
516 }
517 }
518 $dep =
519 array(
520 'name' => $name,
521 'type' => $type);
522 if ($version) {
523 $dep['version'] = $version;
524 if ($operator) {
525 $dep['rel'] = $operator;
526 }
527 }
528
529 if ($found !== false) {
530 $this->_packageXml['release_deps'][$found] = $dep; // overwrite existing dependency
531 } else {
532 $this->_packageXml['release_deps'][] = $dep; // add new dependency
533 }
534 }
535
536 /**
537 * Writes the package.xml file out with the newly created <release></release> tag
538 *
539 * ALWAYS use {@link debugPackageFile} to verify that output is correct before
540 * overwriting your package.xml
541 * @param boolean null if no debugging, true if web interface, false if command-line
542 * @throws PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS
543 * @throws PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS
544 * @throws PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE
545 * @throws PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE
546 * @throws PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE
547 * @throws PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE
548 * @return void|PEAR_Error
549 */
550 function writePackageFile($debuginterface = null)
551 {
552 if (!$this->_packageXml) {
553 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_RUN_SETOPTIONS);
554 }
555 if (!isset($this->_packageXml['maintainers']) || empty($this->_packageXml['maintainers'])) {
556 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_ADD_MAINTAINERS);
557 }
558 extract($this->_options);
559 $date = date('Y-m-d');
560 if (isset($package)) {
561 $this->_packageXml['package'] = $package;
562 }
563 if (isset($summary)) {
564 $this->_packageXml['summary'] = $summary;
565 }
566 if (isset($description)) {
567 $this->_packageXml['description'] = $description;
568 }
569 $this->_packageXml['release_date'] = $date;
570 $this->_packageXml['version'] = $version;
571 $this->_packageXml['release_license'] = $license;
572 $this->_packageXml['release_state'] = $state;
573 $this->_packageXml['release_notes'] = $notes;
574 $this->_pear = new PEAR_Common;
575 $this->_packageXml['filelist'] = $this->_getFileList();
576 if (PEAR::isError($this->_packageXml['filelist'])) {
577 return $this->_packageXml['filelist'];
578 }
579 if (isset($this->_pear->pkginfo['provides'])) {
580 $this->_packageXml['provides'] = $this->_pear->pkginfo['provides'];
581 }
582 $this->_packageXml['release_deps'] = $this->_getDependencies();
583 $this->_updateChangeLog();
584 $common = &$this->_pear;
585 $packagexml = $common->xmlFromInfo($this->_packageXml);
586 if (!strpos($packagexml, '<!DOCTYPE')) {
587 // hack to fix pear
588 $packagexml = str_replace('<package version="1.0">',
589 "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n<package version=\"1.0\">",
590 $packagexml);
591 }
592 if (isset($debuginterface)) {
593 if ($debuginterface) {
594 echo '<pre>' . htmlentities($packagexml) . '</pre>';
595 } else {
596 echo $packagexml;
597 }
598 return true;
599 }
600 $outputdir = ($this->_options['outputdirectory'] ?
601 $this->_options['outputdirectory'] : $this->_options['packagedirectory']);
602 if (is_writable($this->_options['packagedirectory'] . $this->_options['packagefile'])) {
603 if ($fp = @fopen($outputdir . $this->_options['packagefile'] . '.tmp', "w")) {
604 $written = @fwrite($fp, $packagexml);
605 @fclose($fp);
606 if ($written === false) {
607 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTWRITE_PKGFILE);
608 }
609 if (!@copy($outputdir . $this->_options['packagefile'] . '.tmp',
610 $outputdir . $this->_options['packagefile'])) {
611 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTCOPY_PKGFILE);
612 } else {
613 @unlink($outputdir . $this->_options['packagefile'] . '.tmp');
614 return true;
615 }
616 } else {
617 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_CANTOPEN_TMPPKGFILE,
618 $outputdir . $this->_options['packagefile'] . '.tmp');
619 }
620 } else {
621 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_DEST_UNWRITABLE, $outputdir);
622 }
623 }
624
625 /**
626 * ALWAYS use this to test output before overwriting your package.xml!!
627 * @uses writePackageFile() calls with the debug parameter set based on
628 * whether it is called from the command-line or web interface
629 */
630 function debugPackageFile()
631 {
632 $webinterface = isset($_SERVER['PATH_TRANSLATED']);
633 return $this->writePackageFile($webinterface);
634 }
635
636 /**
637 * Utility function to shorten error generation code
638 *
639 * {@source }
640 * @return PEAR_Error
641 * @static
642 */
643 function raiseError($code, $i1 = '', $i2 = '')
644 {
645 return PEAR::raiseError('PEAR_PackageFileManager Error: ' .
646 sprintf($GLOBALS['_PEAR_PACKAGEFILEMANAGER_ERRORS'][$this->_options['lang']][$code],
647 $i1, $i2));
648 }
649
650 /**
651 * Uses {@link PEAR_Common::analyzeSourceCode()} and {@link PEAR_Common::buildProvidesArray()}
652 * to create the <provides></provides> section of the package.xml
653 * @param PEAR_Common
654 * @param string path to source file
655 * @access private
656 */
657 function _addProvides(&$pear, $file)
658 {
659 $pear->buildProvidesArray($pear->analyzeSourceCode($file));
660 }
661
662 /**
663 * @uses getDirTag() generate the xml from the array
664 * @return string
665 * @access private
666 */
667 function _getFileList()
668 {
669 $generatorclass = 'PEAR_PackageFileManager_' . $this->_options['filelistgenerator'];
670 $generator = new $generatorclass($this, $this->_options);
671 return $this->_getDirTag($generator->getFileList());
672 }
673
674 /**
675 * Recursively generate the <filelist> section's <dir> and <file> tags
676 * @param array|PEAR_Errorthe sorted directory structure, or an error
677 * from filelist generation
678 * @param false|stringwhether the parent directory has a role this should
679 * inherit
680 * @param integer indentation level
681 * @return array|PEAR_Error
682 * @access private
683 */
684 function _getDirTag($struc, $role=false)
685 {
686 if (PEAR::isError($struc)) {
687 return $struc;
688 }
689 extract($this->_options);
690 $ret = array();
691 foreach($struc as $dir => $files) {
692 if ($dir === '/') {
693 return $this->_getDirTag($struc[$dir], $role);
694 } else {
695 if (!isset($files['file'])) {
696 $myrole = '';
697 if ($role) {
698 $myrole = $role;
699 } elseif (isset($dir_roles[$dir])) {
700 $myrole = $dir_roles[$dir];
701 }
702 $ret = array_merge($ret, $this->_getDirTag($files, $myrole));
703 } else {
704 $myrole = '';
705 if (!$role)
706 {
707 $myrole = false;
708 if (isset($exceptions[$files['file']])) {
709 $myrole = $exceptions[$files['file']];
710 } elseif (isset($roles[$files['ext']])) {
711 $myrole = $roles[$files['ext']];
712 } else {
713 $myrole = $roles['*'];
714 }
715 } else {
716 $myrole = $role;
717 }
718 if (isset($installexceptions[$files['file']])) {
719 $bi = $installexceptions[$files['file']];
720 } else {
721 $bi = $baseinstalldir;
722 }
723 $ret[$files['path']] = array('role' => $myrole, 'baseinstalldir' => $bi);
724 if ($myrole == 'php') {
725 $this->_addProvides($this->_pear, $files['fullpath']);
726 }
727 }
728 }
729 }
730 return $ret;
731 }
732
733 /**
734 * Retrieve the 'deps' option passed to the constructor
735 * @access private
736 * @return array
737 */
738 function _getDependencies()
739 {
740 if ($this->_packageXml['release_deps']) {
741 return $this->_packageXml['release_deps'];
742 } else {
743 return array();
744 }
745 }
746
747 /**
748 * Creates a changelog entry with the current release
749 * notes and dates, or overwrites a previous creation
750 * @access private
751 */
752 function _updateChangeLog()
753 {
754 $curlog = false;
755 foreach($this->_packageXml['changelog'] as $index => $changelog) {
756 if ($changelog['version'] == $this->_options['version']) {
757 $curlog = $index;
758 }
759 $this->_packageXml['changelog'][$index]['release_notes'] = trim($changelog['release_notes']);
760 // the parsing of the release notes adds a \n for some reason
761 }
762 $notes = ($this->_options['changelognotes'] ?
763 $this->_options['changelognotes'] : $this->_options['notes']);
764 $changelog = array('version' => $this->_options['version'],
765 'release_date' => date('Y-m-d'),
766 'release_license' => $this->_options['license'],
767 'release_state' => $this->_options['state'],
768 'release_notes' => $notes,
769 );
770 if ($curlog !== false) {
771 $this->_packageXml['changelog'][$curlog] = $changelog;
772 } else {
773 $this->_packageXml['changelog'][] = $changelog;
774 }
775 }
776
777 /**
778 * @return true|PEAR_Error
779 * @uses _generateNewPackageXML() if no package.xml is found, it
780 * calls this to create a new one
781 * @param string full path to package file
782 * @param string name of package file
783 * @throws PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST
784 * @access private
785 */
786 function _getExistingPackageXML($path, $packagefile = 'package.xml')
787 {
788 if (@is_dir($path)) {
789 $contents = @file_get_contents($path . $packagefile);
790 if (!$contents) {
791 return $this->_generateNewPackageXML();
792 } else {
793 $common = new PEAR_Common;
794 $this->_packageXml = $common->infoFromString($contents);
795 if (PEAR::isError($this->_packageXml)) {
796 return $this->_packageXml;
797 }
798 if ($this->_options['deps'] !== false) {
799 $this->_packageXml['release_deps'] = $this->_options['deps'];
800 } else {
801 $this->_options['deps'] = $this->_packageXml['release_deps'];
802 }
803 if ($this->_options['maintainers'] !== false) {
804 $this->_packageXml['maintainers'] = $this->_options['maintainers'];
805 } else {
806 $this->_options['maintainers'] = $this->_packageXml['maintainers'];
807 }
808 unset($this->_packageXml['filelist']);
809 }
810 return true;
811 } else {
812 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_PATH_DOESNT_EXIST,
813 $path);
814 }
815 }
816
817 /**
818 * Create the structure for a new package.xml
819 *
820 * @uses $_packageXml emulates reading in a package.xml
821 * by using the package, summary and description
822 * options
823 * @return true|PEAR_Error
824 * @access private
825 */
826 function _generateNewPackageXML()
827 {
828 if (!isset($this->_options['package'])) {
829 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOPACKAGE);
830 }
831 if (!isset($this->_options['summary'])) {
832 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NOSUMMARY);
833 }
834 if (!isset($this->_options['description'])) {
835 return $this->raiseError(PEAR_PACKAGEFILEMANAGER_NODESC);
836 }
837 $this->_packageXml = array();
838 $this->_packageXml['package'] = $this->_options['package'];
839 $this->_packageXml['summary'] = $this->_options['summary'];
840 $this->_packageXml['description'] = $this->_options['description'];
841 $this->_packageXml['changelog'] = array();
842 if ($this->_options['deps'] !== false) {
843 $this->_packageXml['release_deps'] = $this->_options['deps'];
844 } else {
845 $this->_packageXml['release_deps'] = $this->_options['deps'] = array();
846 }
847 if ($this->_options['maintainers'] !== false) {
848 $this->_packageXml['maintainers'] = $this->_options['maintainers'];
849 } else {
850 $this->_packageXml['maintainers'] = $this->_options['maintainers'] = array();
851 }
852 return true;
853 }
854 }
855
if (!function_exists('file_get_contents')) {
856 /**
857 * @ignore
858 */
859 function file_get_contents($path, $use_include_path = null, $context = null)
860 {
861 $a = @file($path, $use_include_path, $context);
862 if (is_array($a)) {
863 return implode('', $a);
864 } else {
865 return false;
866 }
867 }
868 }
869 ?>