1<?php
2/**
3 * PEAR_Config, customized configuration handling for the PEAR Installer
4 *
5 * PHP versions 4 and 5
6 *
7 * @category   pear
8 * @package    PEAR
9 * @author     Stig Bakken <ssb@php.net>
10 * @author     Greg Beaver <cellog@php.net>
11 * @copyright  1997-2009 The Authors
12 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
13 * @link       http://pear.php.net/package/PEAR
14 * @since      File available since Release 0.1
15 */
16
17/**
18 * Required for error handling
19 */
20require_once 'PEAR.php';
21require_once 'PEAR/Registry.php';
22require_once 'PEAR/Installer/Role.php';
23require_once 'System.php';
24
25/**
26 * Last created PEAR_Config instance.
27 * @var object
28 */
29$GLOBALS['_PEAR_Config_instance'] = null;
30if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) {
31    $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear';
32} else {
33    $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR;
34}
35
36// Below we define constants with default values for all configuration
37// parameters except username/password.  All of them can have their
38// defaults set through environment variables.  The reason we use the
39// PHP_ prefix is for some security, PHP protects environment
40// variables starting with PHP_*.
41
42// default channel and preferred mirror is based on whether we are invoked through
43// the "pear" or the "pecl" command
44if (!defined('PEAR_RUNTYPE')) {
45    define('PEAR_RUNTYPE', 'pear');
46}
47
48if (PEAR_RUNTYPE == 'pear') {
49    define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net');
50} else {
51    define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net');
52}
53
54if (getenv('PHP_PEAR_SYSCONF_DIR')) {
55    define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR'));
56} elseif (getenv('SystemRoot')) {
57    define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot'));
58} else {
59    define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR);
60}
61
62// Default for master_server
63if (getenv('PHP_PEAR_MASTER_SERVER')) {
64    define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER'));
65} else {
66    define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net');
67}
68
69// Default for http_proxy
70if (getenv('PHP_PEAR_HTTP_PROXY')) {
71    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY'));
72} elseif (getenv('http_proxy')) {
73    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy'));
74} else {
75    define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', '');
76}
77
78// Default for php_dir
79if (getenv('PHP_PEAR_INSTALL_DIR')) {
80    define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR'));
81} else {
82    if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) {
83        define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
84    } else {
85        define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
86    }
87}
88
89// Default for metadata_dir
90if (getenv('PHP_PEAR_METADATA_DIR')) {
91    define('PEAR_CONFIG_DEFAULT_METADATA_DIR', getenv('PHP_PEAR_METADATA_DIR'));
92} else {
93    define('PEAR_CONFIG_DEFAULT_METADATA_DIR', '');
94}
95
96// Default for ext_dir
97if (getenv('PHP_PEAR_EXTENSION_DIR')) {
98    define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR'));
99} else {
100    if (ini_get('extension_dir')) {
101        define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir'));
102    } elseif (defined('PEAR_EXTENSION_DIR') &&
103              file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) {
104        define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR);
105    } elseif (defined('PHP_EXTENSION_DIR')) {
106        define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR);
107    } else {
108        define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.');
109    }
110}
111
112// Default for doc_dir
113if (getenv('PHP_PEAR_DOC_DIR')) {
114    define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR'));
115} else {
116    define('PEAR_CONFIG_DEFAULT_DOC_DIR',
117           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs');
118}
119
120// Default for bin_dir
121if (getenv('PHP_PEAR_BIN_DIR')) {
122    define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR'));
123} else {
124    define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR);
125}
126
127// Default for data_dir
128if (getenv('PHP_PEAR_DATA_DIR')) {
129    define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR'));
130} else {
131    define('PEAR_CONFIG_DEFAULT_DATA_DIR',
132           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data');
133}
134
135// Default for cfg_dir
136if (getenv('PHP_PEAR_CFG_DIR')) {
137    define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR'));
138} else {
139    define('PEAR_CONFIG_DEFAULT_CFG_DIR',
140           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg');
141}
142
143// Default for www_dir
144if (getenv('PHP_PEAR_WWW_DIR')) {
145    define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR'));
146} else {
147    define('PEAR_CONFIG_DEFAULT_WWW_DIR',
148           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www');
149}
150
151// Default for man_dir
152if (getenv('PHP_PEAR_MAN_DIR')) {
153    define('PEAR_CONFIG_DEFAULT_MAN_DIR', getenv('PHP_PEAR_MAN_DIR'));
154} else {
155    if (defined('PHP_MANDIR')) { // Added in PHP5.3.7
156        define('PEAR_CONFIG_DEFAULT_MAN_DIR', PHP_MANDIR);
157    } else {
158        define('PEAR_CONFIG_DEFAULT_MAN_DIR', PHP_PREFIX . DIRECTORY_SEPARATOR .
159           'local' . DIRECTORY_SEPARATOR .'man');
160    }
161}
162
163// Default for test_dir
164if (getenv('PHP_PEAR_TEST_DIR')) {
165    define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR'));
166} else {
167    define('PEAR_CONFIG_DEFAULT_TEST_DIR',
168           $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests');
169}
170
171// Default for temp_dir
172if (getenv('PHP_PEAR_TEMP_DIR')) {
173    define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR'));
174} else {
175    define('PEAR_CONFIG_DEFAULT_TEMP_DIR',
176           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
177           DIRECTORY_SEPARATOR . 'temp');
178}
179
180// Default for cache_dir
181if (getenv('PHP_PEAR_CACHE_DIR')) {
182    define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR'));
183} else {
184    define('PEAR_CONFIG_DEFAULT_CACHE_DIR',
185           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
186           DIRECTORY_SEPARATOR . 'cache');
187}
188
189// Default for download_dir
190if (getenv('PHP_PEAR_DOWNLOAD_DIR')) {
191    define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR'));
192} else {
193    define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR',
194           System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
195           DIRECTORY_SEPARATOR . 'download');
196}
197
198// Default for php_bin
199if (getenv('PHP_PEAR_PHP_BIN')) {
200    define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN'));
201} else {
202    define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR.
203           DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : ''));
204}
205
206// Default for verbose
207if (getenv('PHP_PEAR_VERBOSE')) {
208    define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE'));
209} else {
210    define('PEAR_CONFIG_DEFAULT_VERBOSE', 1);
211}
212
213// Default for preferred_state
214if (getenv('PHP_PEAR_PREFERRED_STATE')) {
215    define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE'));
216} else {
217    define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable');
218}
219
220// Default for umask
221if (getenv('PHP_PEAR_UMASK')) {
222    define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK'));
223} else {
224    define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask()));
225}
226
227// Default for cache_ttl
228if (getenv('PHP_PEAR_CACHE_TTL')) {
229    define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL'));
230} else {
231    define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600);
232}
233
234// Default for sig_type
235if (getenv('PHP_PEAR_SIG_TYPE')) {
236    define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE'));
237} else {
238    define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg');
239}
240
241// Default for sig_bin
242if (getenv('PHP_PEAR_SIG_BIN')) {
243    define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN'));
244} else {
245    define('PEAR_CONFIG_DEFAULT_SIG_BIN',
246           System::which(
247               'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg'));
248}
249
250// Default for sig_keydir
251if (getenv('PHP_PEAR_SIG_KEYDIR')) {
252    define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR'));
253} else {
254    define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR',
255           PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys');
256}
257
258/**
259 * This is a class for storing configuration data, keeping track of
260 * which are system-defined, user-defined or defaulted.
261 * @category   pear
262 * @package    PEAR
263 * @author     Stig Bakken <ssb@php.net>
264 * @author     Greg Beaver <cellog@php.net>
265 * @copyright  1997-2009 The Authors
266 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
267 * @version    Release: @package_version@
268 * @link       http://pear.php.net/package/PEAR
269 * @since      Class available since Release 0.1
270 */
271class PEAR_Config extends PEAR
272{
273    /**
274     * Array of config files used.
275     *
276     * @var array layer => config file
277     */
278    var $files = array(
279        'system' => '',
280        'user' => '',
281        );
282
283    var $layers = array();
284
285    /**
286     * Configuration data, two-dimensional array where the first
287     * dimension is the config layer ('user', 'system' and 'default'),
288     * and the second dimension is keyname => value.
289     *
290     * The order in the first dimension is important!  Earlier
291     * layers will shadow later ones when a config value is
292     * requested (if a 'user' value exists, it will be returned first,
293     * then 'system' and finally 'default').
294     *
295     * @var array layer => array(keyname => value, ...)
296     */
297    var $configuration = array(
298        'user' => array(),
299        'system' => array(),
300        'default' => array(),
301        );
302
303    /**
304     * Configuration values that can be set for a channel
305     *
306     * All other configuration values can only have a global value
307     * @var array
308     * @access private
309     */
310    var $_channelConfigInfo = array(
311        'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir',
312        'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username',
313        'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini'
314        );
315
316    /**
317     * Channels that can be accessed
318     * @see setChannels()
319     * @var array
320     * @access private
321     */
322    var $_channels = array('pear.php.net', 'pecl.php.net', '__uri');
323
324    /**
325     * This variable is used to control the directory values returned
326     * @see setInstallRoot();
327     * @var string|false
328     * @access private
329     */
330    var $_installRoot = false;
331
332    /**
333     * If requested, this will always refer to the registry
334     * contained in php_dir
335     * @var PEAR_Registry
336     */
337    var $_registry = array();
338
339    /**
340     * @var array
341     * @access private
342     */
343    var $_regInitialized = array();
344
345    /**
346     * @var bool
347     * @access private
348     */
349    var $_noRegistry = false;
350
351    /**
352     * amount of errors found while parsing config
353     * @var integer
354     * @access private
355     */
356    var $_errorsFound = 0;
357    var $_lastError = null;
358
359    /**
360     * Information about the configuration data.  Stores the type,
361     * default value and a documentation string for each configuration
362     * value.
363     *
364     * @var array layer => array(infotype => value, ...)
365     */
366    var $configuration_info = array(
367        // Channels/Internet Access
368        'default_channel' => array(
369            'type' => 'string',
370            'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
371            'doc' => 'the default channel to use for all non explicit commands',
372            'prompt' => 'Default Channel',
373            'group' => 'Internet Access',
374            ),
375        'preferred_mirror' => array(
376            'type' => 'string',
377            'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
378            'doc' => 'the default server or mirror to use for channel actions',
379            'prompt' => 'Default Channel Mirror',
380            'group' => 'Internet Access',
381            ),
382        'remote_config' => array(
383            'type' => 'password',
384            'default' => '',
385            'doc' => 'ftp url of remote configuration file to use for synchronized install',
386            'prompt' => 'Remote Configuration File',
387            'group' => 'Internet Access',
388            ),
389        'auto_discover' => array(
390            'type' => 'integer',
391            'default' => 0,
392            'doc' => 'whether to automatically discover new channels',
393            'prompt' => 'Auto-discover new Channels',
394            'group' => 'Internet Access',
395            ),
396        // Internet Access
397        'master_server' => array(
398            'type' => 'string',
399            'default' => 'pear.php.net',
400            'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]',
401            'prompt' => 'PEAR server [DEPRECATED]',
402            'group' => 'Internet Access',
403            ),
404        'http_proxy' => array(
405            'type' => 'string',
406            'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY,
407            'doc' => 'HTTP proxy (host:port) to use when downloading packages',
408            'prompt' => 'HTTP Proxy Server Address',
409            'group' => 'Internet Access',
410            ),
411        // File Locations
412        'php_dir' => array(
413            'type' => 'directory',
414            'default' => PEAR_CONFIG_DEFAULT_PHP_DIR,
415            'doc' => 'directory where .php files are installed',
416            'prompt' => 'PEAR directory',
417            'group' => 'File Locations',
418            ),
419        'ext_dir' => array(
420            'type' => 'directory',
421            'default' => PEAR_CONFIG_DEFAULT_EXT_DIR,
422            'doc' => 'directory where loadable extensions are installed',
423            'prompt' => 'PHP extension directory',
424            'group' => 'File Locations',
425            ),
426        'doc_dir' => array(
427            'type' => 'directory',
428            'default' => PEAR_CONFIG_DEFAULT_DOC_DIR,
429            'doc' => 'directory where documentation is installed',
430            'prompt' => 'PEAR documentation directory',
431            'group' => 'File Locations',
432            ),
433        'bin_dir' => array(
434            'type' => 'directory',
435            'default' => PEAR_CONFIG_DEFAULT_BIN_DIR,
436            'doc' => 'directory where executables are installed',
437            'prompt' => 'PEAR executables directory',
438            'group' => 'File Locations',
439            ),
440        'data_dir' => array(
441            'type' => 'directory',
442            'default' => PEAR_CONFIG_DEFAULT_DATA_DIR,
443            'doc' => 'directory where data files are installed',
444            'prompt' => 'PEAR data directory',
445            'group' => 'File Locations (Advanced)',
446            ),
447        'cfg_dir' => array(
448            'type' => 'directory',
449            'default' => PEAR_CONFIG_DEFAULT_CFG_DIR,
450            'doc' => 'directory where modifiable configuration files are installed',
451            'prompt' => 'PEAR configuration file directory',
452            'group' => 'File Locations (Advanced)',
453            ),
454        'www_dir' => array(
455            'type' => 'directory',
456            'default' => PEAR_CONFIG_DEFAULT_WWW_DIR,
457            'doc' => 'directory where www frontend files (html/js) are installed',
458            'prompt' => 'PEAR www files directory',
459            'group' => 'File Locations (Advanced)',
460            ),
461        'man_dir' => array(
462            'type' => 'directory',
463            'default' => PEAR_CONFIG_DEFAULT_MAN_DIR,
464            'doc' => 'directory where unix manual pages are installed',
465            'prompt' => 'Systems manpage files directory',
466            'group' => 'File Locations (Advanced)',
467            ),
468        'test_dir' => array(
469            'type' => 'directory',
470            'default' => PEAR_CONFIG_DEFAULT_TEST_DIR,
471            'doc' => 'directory where regression tests are installed',
472            'prompt' => 'PEAR test directory',
473            'group' => 'File Locations (Advanced)',
474            ),
475        'cache_dir' => array(
476            'type' => 'directory',
477            'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR,
478            'doc' => 'directory which is used for web service cache',
479            'prompt' => 'PEAR Installer cache directory',
480            'group' => 'File Locations (Advanced)',
481            ),
482        'temp_dir' => array(
483            'type' => 'directory',
484            'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR,
485            'doc' => 'directory which is used for all temp files',
486            'prompt' => 'PEAR Installer temp directory',
487            'group' => 'File Locations (Advanced)',
488            ),
489        'download_dir' => array(
490            'type' => 'directory',
491            'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR,
492            'doc' => 'directory which is used for all downloaded files',
493            'prompt' => 'PEAR Installer download directory',
494            'group' => 'File Locations (Advanced)',
495            ),
496        'php_bin' => array(
497            'type' => 'file',
498            'default' => PEAR_CONFIG_DEFAULT_PHP_BIN,
499            'doc' => 'PHP CLI/CGI binary for executing scripts',
500            'prompt' => 'PHP CLI/CGI binary',
501            'group' => 'File Locations (Advanced)',
502            ),
503        'php_prefix' => array(
504            'type' => 'string',
505            'default' => '',
506            'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs',
507            'prompt' => '--program-prefix passed to PHP\'s ./configure',
508            'group' => 'File Locations (Advanced)',
509            ),
510        'php_suffix' => array(
511            'type' => 'string',
512            'default' => '',
513            'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs',
514            'prompt' => '--program-suffix passed to PHP\'s ./configure',
515            'group' => 'File Locations (Advanced)',
516            ),
517        'php_ini' => array(
518            'type' => 'file',
519            'default' => '',
520            'doc' => 'location of php.ini in which to enable PECL extensions on install',
521            'prompt' => 'php.ini location',
522            'group' => 'File Locations (Advanced)',
523            ),
524        'metadata_dir' => array(
525            'type' => 'directory',
526            'default' => PEAR_CONFIG_DEFAULT_METADATA_DIR,
527            'doc' => 'directory where metadata files are installed (registry, filemap, channels, ...)',
528            'prompt' => 'PEAR metadata directory',
529            'group' => 'File Locations (Advanced)',
530            ),
531        // Maintainers
532        'username' => array(
533            'type' => 'string',
534            'default' => '',
535            'doc' => '(maintainers) your PEAR account name',
536            'prompt' => 'PEAR username (for maintainers)',
537            'group' => 'Maintainers',
538            ),
539        'password' => array(
540            'type' => 'password',
541            'default' => '',
542            'doc' => '(maintainers) your PEAR account password',
543            'prompt' => 'PEAR password (for maintainers)',
544            'group' => 'Maintainers',
545            ),
546        // Advanced
547        'verbose' => array(
548            'type' => 'integer',
549            'default' => PEAR_CONFIG_DEFAULT_VERBOSE,
550            'doc' => 'verbosity level
5510: really quiet
5521: somewhat quiet
5532: verbose
5543: debug',
555            'prompt' => 'Debug Log Level',
556            'group' => 'Advanced',
557            ),
558        'preferred_state' => array(
559            'type' => 'set',
560            'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE,
561            'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified',
562            'valid_set' => array(
563                'stable', 'beta', 'alpha', 'devel', 'snapshot'),
564            'prompt' => 'Preferred Package State',
565            'group' => 'Advanced',
566            ),
567        'umask' => array(
568            'type' => 'mask',
569            'default' => PEAR_CONFIG_DEFAULT_UMASK,
570            'doc' => 'umask used when creating files (Unix-like systems only)',
571            'prompt' => 'Unix file mask',
572            'group' => 'Advanced',
573            ),
574        'cache_ttl' => array(
575            'type' => 'integer',
576            'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL,
577            'doc' => 'amount of secs where the local cache is used and not updated',
578            'prompt' => 'Cache TimeToLive',
579            'group' => 'Advanced',
580            ),
581        'sig_type' => array(
582            'type' => 'set',
583            'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE,
584            'doc' => 'which package signature mechanism to use',
585            'valid_set' => array('gpg'),
586            'prompt' => 'Package Signature Type',
587            'group' => 'Maintainers',
588            ),
589        'sig_bin' => array(
590            'type' => 'string',
591            'default' => PEAR_CONFIG_DEFAULT_SIG_BIN,
592            'doc' => 'which package signature mechanism to use',
593            'prompt' => 'Signature Handling Program',
594            'group' => 'Maintainers',
595            ),
596        'sig_keyid' => array(
597            'type' => 'string',
598            'default' => '',
599            'doc' => 'which key to use for signing with',
600            'prompt' => 'Signature Key Id',
601            'group' => 'Maintainers',
602            ),
603        'sig_keydir' => array(
604            'type' => 'directory',
605            'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR,
606            'doc' => 'directory where signature keys are located',
607            'prompt' => 'Signature Key Directory',
608            'group' => 'Maintainers',
609            ),
610        // __channels is reserved - used for channel-specific configuration
611        );
612
613    /**
614     * Constructor.
615     *
616     * @param string file to read user-defined options from
617     * @param string file to read system-wide defaults from
618     * @param bool   determines whether a registry object "follows"
619     *               the value of php_dir (is automatically created
620     *               and moved when php_dir is changed)
621     * @param bool   if true, fails if configuration files cannot be loaded
622     *
623     * @access public
624     *
625     * @see PEAR_Config::singleton
626     */
627    function __construct($user_file = '', $system_file = '', $ftp_file = false,
628                         $strict = true)
629    {
630        parent::__construct();
631        PEAR_Installer_Role::initializeConfig($this);
632        $sl = DIRECTORY_SEPARATOR;
633        if (empty($user_file)) {
634            if (OS_WINDOWS) {
635                $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini';
636            } else {
637                $user_file = getenv('HOME') . $sl . '.pearrc';
638            }
639        }
640
641        if (empty($system_file)) {
642            $system_file = PEAR_CONFIG_SYSCONFDIR . $sl;
643            if (OS_WINDOWS) {
644                $system_file .= 'pearsys.ini';
645            } else {
646                $system_file .= 'pear.conf';
647            }
648        }
649
650        $this->layers = array_keys($this->configuration);
651        $this->files['user']   = $user_file;
652        $this->files['system'] = $system_file;
653        if ($user_file && file_exists($user_file)) {
654            $this->pushErrorHandling(PEAR_ERROR_RETURN);
655            $this->readConfigFile($user_file, 'user', $strict);
656            $this->popErrorHandling();
657            if ($this->_errorsFound > 0) {
658                return;
659            }
660        }
661
662        if ($system_file && @file_exists($system_file)) {
663            $this->mergeConfigFile($system_file, false, 'system', $strict);
664            if ($this->_errorsFound > 0) {
665                return;
666            }
667
668        }
669
670        if (!$ftp_file) {
671            $ftp_file = $this->get('remote_config');
672        }
673
674        if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) {
675            $this->readFTPConfigFile($ftp_file);
676        }
677
678        foreach ($this->configuration_info as $key => $info) {
679            $this->configuration['default'][$key] = $info['default'];
680        }
681
682        $this->_registry['default'] = new PEAR_Registry(
683            $this->configuration['default']['php_dir'], false, false,
684            $this->configuration['default']['metadata_dir']);
685        $this->_registry['default']->setConfig($this, false);
686        $this->_regInitialized['default'] = false;
687        //$GLOBALS['_PEAR_Config_instance'] = &$this;
688    }
689
690    /**
691     * Return the default locations of user and system configuration files
692     */
693    public static function getDefaultConfigFiles()
694    {
695        $sl = DIRECTORY_SEPARATOR;
696        if (OS_WINDOWS) {
697            return array(
698                'user'   => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini',
699                'system' =>  PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini'
700            );
701        }
702
703        return array(
704            'user'   => getenv('HOME') . $sl . '.pearrc',
705            'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf'
706        );
707    }
708
709    /**
710     * Static singleton method.  If you want to keep only one instance
711     * of this class in use, this method will give you a reference to
712     * the last created PEAR_Config object if one exists, or create a
713     * new object.
714     *
715     * @param string (optional) file to read user-defined options from
716     * @param string (optional) file to read system-wide defaults from
717     *
718     * @return object an existing or new PEAR_Config instance
719     *
720     * @see PEAR_Config::PEAR_Config
721     */
722    public static function &singleton($user_file = '', $system_file = '', $strict = true)
723    {
724        if (is_object($GLOBALS['_PEAR_Config_instance'])) {
725            return $GLOBALS['_PEAR_Config_instance'];
726        }
727
728        $t_conf = new PEAR_Config($user_file, $system_file, false, $strict);
729        if ($t_conf->_errorsFound > 0) {
730             return $t_conf->lastError;
731        }
732
733        $GLOBALS['_PEAR_Config_instance'] = &$t_conf;
734        return $GLOBALS['_PEAR_Config_instance'];
735    }
736
737    /**
738     * Determine whether any configuration files have been detected, and whether a
739     * registry object can be retrieved from this configuration.
740     * @return bool
741     * @since PEAR 1.4.0a1
742     */
743    function validConfiguration()
744    {
745        if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) {
746            return true;
747        }
748
749        return false;
750    }
751
752    /**
753     * Reads configuration data from a file.  All existing values in
754     * the config layer are discarded and replaced with data from the
755     * file.
756     * @param string file to read from, if NULL or not specified, the
757     *               last-used file for the same layer (second param) is used
758     * @param string config layer to insert data into ('user' or 'system')
759     * @return bool TRUE on success or a PEAR error on failure
760     */
761    function readConfigFile($file = null, $layer = 'user', $strict = true)
762    {
763        if (empty($this->files[$layer])) {
764            return $this->raiseError("unknown config layer `$layer'");
765        }
766
767        if ($file === null) {
768            $file = $this->files[$layer];
769        }
770
771        $data = $this->_readConfigDataFrom($file);
772        if (PEAR::isError($data)) {
773            if (!$strict) {
774                return true;
775            }
776
777            $this->_errorsFound++;
778            $this->lastError = $data;
779
780            return $data;
781        }
782
783        $this->files[$layer] = $file;
784        $this->_decodeInput($data);
785        $this->configuration[$layer] = $data;
786        $this->_setupChannels();
787        if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
788            $this->_registry[$layer] = new PEAR_Registry(
789                $phpdir, false, false,
790                $this->get('metadata_dir', $layer, 'pear.php.net'));
791            $this->_registry[$layer]->setConfig($this, false);
792            $this->_regInitialized[$layer] = false;
793        } else {
794            unset($this->_registry[$layer]);
795        }
796        return true;
797    }
798
799    /**
800     * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini
801     * @return true|PEAR_Error
802     */
803    function readFTPConfigFile($path)
804    {
805        do { // poor man's try
806            if (!class_exists('PEAR_FTP')) {
807                if (!class_exists('PEAR_Common')) {
808                    require_once 'PEAR/Common.php';
809                }
810                if (PEAR_Common::isIncludeable('PEAR/FTP.php')) {
811                    require_once 'PEAR/FTP.php';
812                }
813            }
814
815            if (!class_exists('PEAR_FTP')) {
816                return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config');
817            }
818
819            $this->_ftp = new PEAR_FTP;
820            $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN);
821            $e = $this->_ftp->init($path);
822            if (PEAR::isError($e)) {
823                $this->_ftp->popErrorHandling();
824                return $e;
825            }
826
827            $tmp = System::mktemp('-d');
828            PEAR_Common::addTempFile($tmp);
829            $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR .
830                'pear.ini', false, FTP_BINARY);
831            if (PEAR::isError($e)) {
832                $this->_ftp->popErrorHandling();
833                return $e;
834            }
835
836            PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini');
837            $this->_ftp->disconnect();
838            $this->_ftp->popErrorHandling();
839            $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini';
840            $e = $this->readConfigFile(null, 'ftp');
841            if (PEAR::isError($e)) {
842                return $e;
843            }
844
845            $fail = array();
846            foreach ($this->configuration_info as $key => $val) {
847                if (in_array($this->getGroup($key),
848                      array('File Locations', 'File Locations (Advanced)')) &&
849                      $this->getType($key) == 'directory') {
850                    // any directory configs must be set for this to work
851                    if (!isset($this->configuration['ftp'][$key])) {
852                        $fail[] = $key;
853                    }
854                }
855            }
856
857            if (!count($fail)) {
858                return true;
859            }
860
861            $fail = '"' . implode('", "', $fail) . '"';
862            unset($this->files['ftp']);
863            unset($this->configuration['ftp']);
864            return PEAR::raiseError('ERROR: Ftp configuration file must set all ' .
865                'directory configuration variables.  These variables were not set: ' .
866                $fail);
867        } while (false); // poor man's catch
868        unset($this->files['ftp']);
869        return PEAR::raiseError('no remote host specified');
870    }
871
872    /**
873     * Reads the existing configurations and creates the _channels array from it
874     */
875    function _setupChannels()
876    {
877        $set = array_flip(array_values($this->_channels));
878        foreach ($this->configuration as $layer => $data) {
879            $i = 1000;
880            if (isset($data['__channels']) && is_array($data['__channels'])) {
881                foreach ($data['__channels'] as $channel => $info) {
882                    $set[$channel] = $i++;
883                }
884            }
885        }
886        $this->_channels = array_values(array_flip($set));
887        $this->setChannels($this->_channels);
888    }
889
890    function deleteChannel($channel)
891    {
892        $ch = strtolower($channel);
893        foreach ($this->configuration as $layer => $data) {
894            if (isset($data['__channels']) && isset($data['__channels'][$ch])) {
895                unset($this->configuration[$layer]['__channels'][$ch]);
896            }
897        }
898
899        $this->_channels = array_flip($this->_channels);
900        unset($this->_channels[$ch]);
901        $this->_channels = array_flip($this->_channels);
902    }
903
904    /**
905     * Merges data into a config layer from a file.  Does the same
906     * thing as readConfigFile, except it does not replace all
907     * existing values in the config layer.
908     * @param string file to read from
909     * @param bool whether to overwrite existing data (default TRUE)
910     * @param string config layer to insert data into ('user' or 'system')
911     * @param string if true, errors are returned if file opening fails
912     * @return bool TRUE on success or a PEAR error on failure
913     */
914    function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true)
915    {
916        if (empty($this->files[$layer])) {
917            return $this->raiseError("unknown config layer `$layer'");
918        }
919
920        if ($file === null) {
921            $file = $this->files[$layer];
922        }
923
924        $data = $this->_readConfigDataFrom($file);
925        if (PEAR::isError($data)) {
926            if (!$strict) {
927                return true;
928            }
929
930            $this->_errorsFound++;
931            $this->lastError = $data;
932
933            return $data;
934        }
935
936        $this->_decodeInput($data);
937        if ($override) {
938            $this->configuration[$layer] =
939                PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data);
940        } else {
941            $this->configuration[$layer] =
942                PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]);
943        }
944
945        $this->_setupChannels();
946        if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
947            $this->_registry[$layer] = new PEAR_Registry(
948                $phpdir, false, false,
949                $this->get('metadata_dir', $layer, 'pear.php.net'));
950            $this->_registry[$layer]->setConfig($this, false);
951            $this->_regInitialized[$layer] = false;
952        } else {
953            unset($this->_registry[$layer]);
954        }
955        return true;
956    }
957
958    /**
959     * @param array
960     * @param array
961     * @return array
962     */
963    public static function arrayMergeRecursive($arr2, $arr1)
964    {
965        $ret = array();
966        foreach ($arr2 as $key => $data) {
967            if (!isset($arr1[$key])) {
968                $ret[$key] = $data;
969                unset($arr1[$key]);
970                continue;
971            }
972            if (is_array($data)) {
973                if (!is_array($arr1[$key])) {
974                    $ret[$key] = $arr1[$key];
975                    unset($arr1[$key]);
976                    continue;
977                }
978                $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]);
979                unset($arr1[$key]);
980            }
981        }
982
983        return array_merge($ret, $arr1);
984    }
985
986    /**
987     * Writes data into a config layer from a file.
988     *
989     * @param string|null file to read from, or null for default
990     * @param string config layer to insert data into ('user' or
991     *               'system')
992     * @param string|null data to write to config file or null for internal data [DEPRECATED]
993     * @return bool TRUE on success or a PEAR error on failure
994     */
995    function writeConfigFile($file = null, $layer = 'user', $data = null)
996    {
997        $this->_lazyChannelSetup($layer);
998        if ($layer == 'both' || $layer == 'all') {
999            foreach ($this->files as $type => $file) {
1000                $err = $this->writeConfigFile($file, $type, $data);
1001                if (PEAR::isError($err)) {
1002                    return $err;
1003                }
1004            }
1005            return true;
1006        }
1007
1008        if (empty($this->files[$layer])) {
1009            return $this->raiseError("unknown config file type `$layer'");
1010        }
1011
1012        if ($file === null) {
1013            $file = $this->files[$layer];
1014        }
1015
1016        $data = ($data === null) ? $this->configuration[$layer] : $data;
1017        $this->_encodeOutput($data);
1018        $opt = array('-p', dirname($file));
1019        if (!@System::mkDir($opt)) {
1020            return $this->raiseError("could not create directory: " . dirname($file));
1021        }
1022
1023        if (file_exists($file) && is_file($file) && !is_writeable($file)) {
1024            return $this->raiseError("no write access to $file!");
1025        }
1026
1027        $fp = @fopen($file, "w");
1028        if (!$fp) {
1029            return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)");
1030        }
1031
1032        $contents = "#PEAR_Config 0.9\n" . serialize($data);
1033        if (!@fwrite($fp, $contents)) {
1034            return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)");
1035        }
1036        return true;
1037    }
1038
1039    /**
1040     * Reads configuration data from a file and returns the parsed data
1041     * in an array.
1042     *
1043     * @param string file to read from
1044     * @return array configuration data or a PEAR error on failure
1045     * @access private
1046     */
1047    function _readConfigDataFrom($file)
1048    {
1049        $fp = false;
1050        if (file_exists($file)) {
1051            $fp = @fopen($file, "r");
1052        }
1053
1054        if (!$fp) {
1055            return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed");
1056        }
1057
1058        $size = filesize($file);
1059        fclose($fp);
1060        $contents = file_get_contents($file);
1061        if (empty($contents)) {
1062            return $this->raiseError('Configuration file "' . $file . '" is empty');
1063        }
1064
1065        $version = false;
1066        if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) {
1067            $version = $matches[1];
1068            $contents = substr($contents, strlen($matches[0]));
1069        } else {
1070            // Museum config file
1071            if (substr($contents,0,2) == 'a:') {
1072                $version = '0.1';
1073            }
1074        }
1075
1076        if ($version && version_compare("$version", '1', '<')) {
1077            // no '@', it is possible that unserialize
1078            // raises a notice but it seems to block IO to
1079            // STDOUT if a '@' is used and a notice is raise
1080            $data = unserialize($contents);
1081
1082            if (!is_array($data) && !$data) {
1083                if ($contents == serialize(false)) {
1084                    $data = array();
1085                } else {
1086                    $err = $this->raiseError("PEAR_Config: bad data in $file");
1087                    return $err;
1088                }
1089            }
1090            if (!is_array($data)) {
1091                if (strlen(trim($contents)) > 0) {
1092                    $error = "PEAR_Config: bad data in $file";
1093                    $err = $this->raiseError($error);
1094                    return $err;
1095                }
1096
1097                $data = array();
1098            }
1099        // add parsing of newer formats here...
1100        } else {
1101            $err = $this->raiseError("$file: unknown version `$version'");
1102            return $err;
1103        }
1104
1105        return $data;
1106    }
1107
1108    /**
1109    * Gets the file used for storing the config for a layer
1110    *
1111    * @param string $layer 'user' or 'system'
1112    */
1113    function getConfFile($layer)
1114    {
1115        return $this->files[$layer];
1116    }
1117
1118    /**
1119     * @param string Configuration class name, used for detecting duplicate calls
1120     * @param array information on a role as parsed from its xml file
1121     * @return true|PEAR_Error
1122     * @access private
1123     */
1124    function _addConfigVars($class, $vars)
1125    {
1126        static $called = array();
1127        if (isset($called[$class])) {
1128            return;
1129        }
1130
1131        $called[$class] = 1;
1132        if (count($vars) > 3) {
1133            return $this->raiseError('Roles can only define 3 new config variables or less');
1134        }
1135
1136        foreach ($vars as $name => $var) {
1137            if (!is_array($var)) {
1138                return $this->raiseError('Configuration information must be an array');
1139            }
1140
1141            if (!isset($var['type'])) {
1142                return $this->raiseError('Configuration information must contain a type');
1143            } elseif (!in_array($var['type'],
1144                    array('string', 'mask', 'password', 'directory', 'file', 'set'))) {
1145                  return $this->raiseError(
1146                      'Configuration type must be one of directory, file, string, ' .
1147                      'mask, set, or password');
1148            }
1149            if (!isset($var['default'])) {
1150                return $this->raiseError(
1151                    'Configuration information must contain a default value ("default" index)');
1152            }
1153
1154            if (is_array($var['default'])) {
1155                $real_default = '';
1156                foreach ($var['default'] as $config_var => $val) {
1157                    if (strpos($config_var, 'text') === 0) {
1158                        $real_default .= $val;
1159                    } elseif (strpos($config_var, 'constant') === 0) {
1160                        if (!defined($val)) {
1161                            return $this->raiseError(
1162                                'Unknown constant "' . $val . '" requested in ' .
1163                                'default value for configuration variable "' .
1164                                $name . '"');
1165                        }
1166
1167                        $real_default .= constant($val);
1168                    } elseif (isset($this->configuration_info[$config_var])) {
1169                        $real_default .=
1170                            $this->configuration_info[$config_var]['default'];
1171                    } else {
1172                        return $this->raiseError(
1173                            'Unknown request for "' . $config_var . '" value in ' .
1174                            'default value for configuration variable "' .
1175                            $name . '"');
1176                    }
1177                }
1178                $var['default'] = $real_default;
1179            }
1180
1181            if ($var['type'] == 'integer') {
1182                $var['default'] = (integer) $var['default'];
1183            }
1184
1185            if (!isset($var['doc'])) {
1186                return $this->raiseError(
1187                    'Configuration information must contain a summary ("doc" index)');
1188            }
1189
1190            if (!isset($var['prompt'])) {
1191                return $this->raiseError(
1192                    'Configuration information must contain a simple prompt ("prompt" index)');
1193            }
1194
1195            if (!isset($var['group'])) {
1196                return $this->raiseError(
1197                    'Configuration information must contain a simple group ("group" index)');
1198            }
1199
1200            if (isset($this->configuration_info[$name])) {
1201                return $this->raiseError('Configuration variable "' . $name .
1202                    '" already exists');
1203            }
1204
1205            $this->configuration_info[$name] = $var;
1206            // fix bug #7351: setting custom config variable in a channel fails
1207            $this->_channelConfigInfo[] = $name;
1208        }
1209
1210        return true;
1211    }
1212
1213    /**
1214     * Encodes/scrambles configuration data before writing to files.
1215     * Currently, 'password' values will be base64-encoded as to avoid
1216     * that people spot cleartext passwords by accident.
1217     *
1218     * @param array (reference) array to encode values in
1219     * @return bool TRUE on success
1220     * @access private
1221     */
1222    function _encodeOutput(&$data)
1223    {
1224        foreach ($data as $key => $value) {
1225            if ($key == '__channels') {
1226                foreach ($data['__channels'] as $channel => $blah) {
1227                    $this->_encodeOutput($data['__channels'][$channel]);
1228                }
1229            }
1230
1231            if (!isset($this->configuration_info[$key])) {
1232                continue;
1233            }
1234
1235            $type = $this->configuration_info[$key]['type'];
1236            switch ($type) {
1237                // we base64-encode passwords so they are at least
1238                // not shown in plain by accident
1239                case 'password': {
1240                    $data[$key] = base64_encode($data[$key]);
1241                    break;
1242                }
1243                case 'mask': {
1244                    $data[$key] = octdec($data[$key]);
1245                    break;
1246                }
1247            }
1248        }
1249
1250        return true;
1251    }
1252
1253    /**
1254     * Decodes/unscrambles configuration data after reading from files.
1255     *
1256     * @param array (reference) array to encode values in
1257     * @return bool TRUE on success
1258     * @access private
1259     *
1260     * @see PEAR_Config::_encodeOutput
1261     */
1262    function _decodeInput(&$data)
1263    {
1264        if (!is_array($data)) {
1265            return true;
1266        }
1267
1268        foreach ($data as $key => $value) {
1269            if ($key == '__channels') {
1270                foreach ($data['__channels'] as $channel => $blah) {
1271                    $this->_decodeInput($data['__channels'][$channel]);
1272                }
1273            }
1274
1275            if (!isset($this->configuration_info[$key])) {
1276                continue;
1277            }
1278
1279            $type = $this->configuration_info[$key]['type'];
1280            switch ($type) {
1281                case 'password': {
1282                    $data[$key] = base64_decode($data[$key]);
1283                    break;
1284                }
1285                case 'mask': {
1286                    $data[$key] = decoct($data[$key]);
1287                    break;
1288                }
1289            }
1290        }
1291
1292        return true;
1293    }
1294
1295    /**
1296     * Retrieve the default channel.
1297     *
1298     * On startup, channels are not initialized, so if the default channel is not
1299     * pear.php.net, then initialize the config.
1300     * @param string registry layer
1301     * @return string|false
1302     */
1303    function getDefaultChannel($layer = null)
1304    {
1305        $ret = false;
1306        if ($layer === null) {
1307            foreach ($this->layers as $layer) {
1308                if (isset($this->configuration[$layer]['default_channel'])) {
1309                    $ret = $this->configuration[$layer]['default_channel'];
1310                    break;
1311                }
1312            }
1313        } elseif (isset($this->configuration[$layer]['default_channel'])) {
1314            $ret = $this->configuration[$layer]['default_channel'];
1315        }
1316
1317        if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') {
1318            $ret = 'pecl.php.net';
1319        }
1320
1321        if ($ret) {
1322            if ($ret != 'pear.php.net') {
1323                $this->_lazyChannelSetup();
1324            }
1325
1326            return $ret;
1327        }
1328
1329        return PEAR_CONFIG_DEFAULT_CHANNEL;
1330    }
1331
1332    /**
1333     * Returns a configuration value, prioritizing layers as per the
1334     * layers property.
1335     *
1336     * @param string config key
1337     * @return mixed the config value, or NULL if not found
1338     * @access public
1339     */
1340    function get($key, $layer = null, $channel = false)
1341    {
1342        if (!isset($this->configuration_info[$key])) {
1343            return null;
1344        }
1345
1346        if ($key == '__channels') {
1347            return null;
1348        }
1349
1350        if ($key == 'default_channel') {
1351            return $this->getDefaultChannel($layer);
1352        }
1353
1354        if (!$channel) {
1355            $channel = $this->getDefaultChannel();
1356        } elseif ($channel != 'pear.php.net') {
1357            $this->_lazyChannelSetup();
1358        }
1359        $channel = strtolower($channel);
1360
1361        $test = (in_array($key, $this->_channelConfigInfo)) ?
1362            $this->_getChannelValue($key, $layer, $channel) :
1363            null;
1364        if ($test !== null) {
1365            if ($this->_installRoot) {
1366                if (in_array($this->getGroup($key),
1367                      array('File Locations', 'File Locations (Advanced)')) &&
1368                      $this->getType($key) == 'directory') {
1369                    return $this->_prependPath($test, $this->_installRoot);
1370                }
1371            }
1372            return $test;
1373        }
1374
1375        if ($layer === null) {
1376            foreach ($this->layers as $layer) {
1377                if (isset($this->configuration[$layer][$key])) {
1378                    $test = $this->configuration[$layer][$key];
1379                    if ($this->_installRoot) {
1380                        if (in_array($this->getGroup($key),
1381                              array('File Locations', 'File Locations (Advanced)')) &&
1382                              $this->getType($key) == 'directory') {
1383                            return $this->_prependPath($test, $this->_installRoot);
1384                        }
1385                    }
1386
1387                    if ($key == 'preferred_mirror') {
1388                        $reg = &$this->getRegistry();
1389                        if (is_object($reg)) {
1390                            $chan = $reg->getChannel($channel);
1391                            if (PEAR::isError($chan)) {
1392                                return $channel;
1393                            }
1394
1395                            if (!$chan->getMirror($test) && $chan->getName() != $test) {
1396                                return $channel; // mirror does not exist
1397                            }
1398                        }
1399                    }
1400                    return $test;
1401                }
1402            }
1403        } elseif (isset($this->configuration[$layer][$key])) {
1404            $test = $this->configuration[$layer][$key];
1405            if ($this->_installRoot) {
1406                if (in_array($this->getGroup($key),
1407                      array('File Locations', 'File Locations (Advanced)')) &&
1408                      $this->getType($key) == 'directory') {
1409                    return $this->_prependPath($test, $this->_installRoot);
1410                }
1411            }
1412
1413            if ($key == 'preferred_mirror') {
1414                $reg = &$this->getRegistry();
1415                if (is_object($reg)) {
1416                    $chan = $reg->getChannel($channel);
1417                    if (PEAR::isError($chan)) {
1418                        return $channel;
1419                    }
1420
1421                    if (!$chan->getMirror($test) && $chan->getName() != $test) {
1422                        return $channel; // mirror does not exist
1423                    }
1424                }
1425            }
1426
1427            return $test;
1428        }
1429
1430        return null;
1431    }
1432
1433    /**
1434     * Returns a channel-specific configuration value, prioritizing layers as per the
1435     * layers property.
1436     *
1437     * @param string config key
1438     * @return mixed the config value, or NULL if not found
1439     * @access private
1440     */
1441    function _getChannelValue($key, $layer, $channel)
1442    {
1443        if ($key == '__channels' || $channel == 'pear.php.net') {
1444            return null;
1445        }
1446
1447        $ret = null;
1448        if ($layer === null) {
1449            foreach ($this->layers as $ilayer) {
1450                if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) {
1451                    $ret = $this->configuration[$ilayer]['__channels'][$channel][$key];
1452                    break;
1453                }
1454            }
1455        } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
1456            $ret = $this->configuration[$layer]['__channels'][$channel][$key];
1457        }
1458
1459        if ($key != 'preferred_mirror') {
1460            return $ret;
1461        }
1462
1463
1464        if ($ret !== null) {
1465            $reg = &$this->getRegistry($layer);
1466            if (is_object($reg)) {
1467                $chan = $reg->getChannel($channel);
1468                if (PEAR::isError($chan)) {
1469                    return $channel;
1470                }
1471
1472                if (!$chan->getMirror($ret) && $chan->getName() != $ret) {
1473                    return $channel; // mirror does not exist
1474                }
1475            }
1476
1477            return $ret;
1478        }
1479
1480        if ($channel != $this->getDefaultChannel($layer)) {
1481            return $channel; // we must use the channel name as the preferred mirror
1482                             // if the user has not chosen an alternate
1483        }
1484
1485        return $this->getDefaultChannel($layer);
1486    }
1487
1488    /**
1489     * Set a config value in a specific layer (defaults to 'user').
1490     * Enforces the types defined in the configuration_info array.  An
1491     * integer config variable will be cast to int, and a set config
1492     * variable will be validated against its legal values.
1493     *
1494     * @param string config key
1495     * @param string config value
1496     * @param string (optional) config layer
1497     * @param string channel to set this value for, or null for global value
1498     * @return bool TRUE on success, FALSE on failure
1499     */
1500    function set($key, $value, $layer = 'user', $channel = false)
1501    {
1502        if ($key == '__channels') {
1503            return false;
1504        }
1505
1506        if (!isset($this->configuration[$layer])) {
1507            return false;
1508        }
1509
1510        if ($key == 'default_channel') {
1511            // can only set this value globally
1512            $channel = 'pear.php.net';
1513            if ($value != 'pear.php.net') {
1514                $this->_lazyChannelSetup($layer);
1515            }
1516        }
1517
1518        if ($key == 'preferred_mirror') {
1519            if ($channel == '__uri') {
1520                return false; // can't set the __uri pseudo-channel's mirror
1521            }
1522
1523            $reg = &$this->getRegistry($layer);
1524            if (is_object($reg)) {
1525                $chan = $reg->getChannel($channel ? $channel : 'pear.php.net');
1526                if (PEAR::isError($chan)) {
1527                    return false;
1528                }
1529
1530                if (!$chan->getMirror($value) && $chan->getName() != $value) {
1531                    return false; // mirror does not exist
1532                }
1533            }
1534        }
1535
1536        if (!isset($this->configuration_info[$key])) {
1537            return false;
1538        }
1539
1540        extract($this->configuration_info[$key]);
1541        switch ($type) {
1542            case 'integer':
1543                $value = (int)$value;
1544                break;
1545            case 'set': {
1546                // If a valid_set is specified, require the value to
1547                // be in the set.  If there is no valid_set, accept
1548                // any value.
1549                if ($valid_set) {
1550                    reset($valid_set);
1551                    if ((key($valid_set) === 0 && !in_array($value, $valid_set)) ||
1552                        (key($valid_set) !== 0 && empty($valid_set[$value])))
1553                    {
1554                        return false;
1555                    }
1556                }
1557                break;
1558            }
1559        }
1560
1561        if (!$channel) {
1562            $channel = $this->get('default_channel', null, 'pear.php.net');
1563        }
1564
1565        if (!in_array($channel, $this->_channels)) {
1566            $this->_lazyChannelSetup($layer);
1567            $reg = &$this->getRegistry($layer);
1568            if ($reg) {
1569                $channel = $reg->channelName($channel);
1570            }
1571
1572            if (!in_array($channel, $this->_channels)) {
1573                return false;
1574            }
1575        }
1576
1577        if ($channel != 'pear.php.net') {
1578            if (in_array($key, $this->_channelConfigInfo)) {
1579                $this->configuration[$layer]['__channels'][$channel][$key] = $value;
1580                return true;
1581            }
1582
1583            return false;
1584        }
1585
1586        if ($key == 'default_channel') {
1587            if (!isset($reg)) {
1588                $reg = &$this->getRegistry($layer);
1589                if (!$reg) {
1590                    $reg = &$this->getRegistry();
1591                }
1592            }
1593
1594            if ($reg) {
1595                $value = $reg->channelName($value);
1596            }
1597
1598            if (!$value) {
1599                return false;
1600            }
1601        }
1602
1603        $this->configuration[$layer][$key] = $value;
1604        if ($key == 'php_dir' && !$this->_noRegistry) {
1605            if (!isset($this->_registry[$layer]) ||
1606                  $value != $this->_registry[$layer]->install_dir) {
1607                $this->_registry[$layer] = new PEAR_Registry($value);
1608                $this->_regInitialized[$layer] = false;
1609                $this->_registry[$layer]->setConfig($this, false);
1610            }
1611        }
1612
1613        return true;
1614    }
1615
1616    function _lazyChannelSetup($uselayer = false)
1617    {
1618        if ($this->_noRegistry) {
1619            return;
1620        }
1621
1622        $merge = false;
1623        foreach ($this->_registry as $layer => $p) {
1624            if ($uselayer && $uselayer != $layer) {
1625                continue;
1626            }
1627
1628            if (!$this->_regInitialized[$layer]) {
1629                if ($layer == 'default' && isset($this->_registry['user']) ||
1630                      isset($this->_registry['system'])) {
1631                    // only use the default registry if there are no alternatives
1632                    continue;
1633                }
1634
1635                if (!is_object($this->_registry[$layer])) {
1636                    if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) {
1637                        $this->_registry[$layer] = new PEAR_Registry(
1638                            $phpdir, false, false,
1639                            $this->get('metadata_dir', $layer, 'pear.php.net'));
1640                        $this->_registry[$layer]->setConfig($this, false);
1641                        $this->_regInitialized[$layer] = false;
1642                    } else {
1643                        unset($this->_registry[$layer]);
1644                        return;
1645                    }
1646                }
1647
1648                $this->setChannels($this->_registry[$layer]->listChannels(), $merge);
1649                $this->_regInitialized[$layer] = true;
1650                $merge = true;
1651            }
1652        }
1653    }
1654
1655    /**
1656     * Set the list of channels.
1657     *
1658     * This should be set via a call to {@link PEAR_Registry::listChannels()}
1659     * @param array
1660     * @param bool
1661     * @return bool success of operation
1662     */
1663    function setChannels($channels, $merge = false)
1664    {
1665        if (!is_array($channels)) {
1666            return false;
1667        }
1668
1669        if ($merge) {
1670            $this->_channels = array_merge($this->_channels, $channels);
1671        } else {
1672            $this->_channels = $channels;
1673        }
1674
1675        foreach ($channels as $channel) {
1676            $channel = strtolower($channel);
1677            if ($channel == 'pear.php.net') {
1678                continue;
1679            }
1680
1681            foreach ($this->layers as $layer) {
1682                if (!isset($this->configuration[$layer]['__channels'])) {
1683                    $this->configuration[$layer]['__channels'] = array();
1684                }
1685                if (!isset($this->configuration[$layer]['__channels'][$channel])
1686                      || !is_array($this->configuration[$layer]['__channels'][$channel])) {
1687                    $this->configuration[$layer]['__channels'][$channel] = array();
1688                }
1689            }
1690        }
1691
1692        return true;
1693    }
1694
1695    /**
1696     * Get the type of a config value.
1697     *
1698     * @param string  config key
1699     *
1700     * @return string type, one of "string", "integer", "file",
1701     * "directory", "set" or "password".
1702     *
1703     * @access public
1704     *
1705     */
1706    function getType($key)
1707    {
1708        if (isset($this->configuration_info[$key])) {
1709            return $this->configuration_info[$key]['type'];
1710        }
1711        return false;
1712    }
1713
1714    /**
1715     * Get the documentation for a config value.
1716     *
1717     * @param string  config key
1718     * @return string documentation string
1719     *
1720     * @access public
1721     *
1722     */
1723    function getDocs($key)
1724    {
1725        if (isset($this->configuration_info[$key])) {
1726            return $this->configuration_info[$key]['doc'];
1727        }
1728
1729        return false;
1730    }
1731
1732    /**
1733     * Get the short documentation for a config value.
1734     *
1735     * @param string  config key
1736     * @return string short documentation string
1737     *
1738     * @access public
1739     *
1740     */
1741    function getPrompt($key)
1742    {
1743        if (isset($this->configuration_info[$key])) {
1744            return $this->configuration_info[$key]['prompt'];
1745        }
1746
1747        return false;
1748    }
1749
1750    /**
1751     * Get the parameter group for a config key.
1752     *
1753     * @param string  config key
1754     * @return string parameter group
1755     *
1756     * @access public
1757     *
1758     */
1759    function getGroup($key)
1760    {
1761        if (isset($this->configuration_info[$key])) {
1762            return $this->configuration_info[$key]['group'];
1763        }
1764
1765        return false;
1766    }
1767
1768    /**
1769     * Get the list of parameter groups.
1770     *
1771     * @return array list of parameter groups
1772     *
1773     * @access public
1774     *
1775     */
1776    function getGroups()
1777    {
1778        $tmp = array();
1779        foreach ($this->configuration_info as $key => $info) {
1780            $tmp[$info['group']] = 1;
1781        }
1782
1783        return array_keys($tmp);
1784    }
1785
1786    /**
1787     * Get the list of the parameters in a group.
1788     *
1789     * @param string $group parameter group
1790     * @return array list of parameters in $group
1791     *
1792     * @access public
1793     *
1794     */
1795    function getGroupKeys($group)
1796    {
1797        $keys = array();
1798        foreach ($this->configuration_info as $key => $info) {
1799            if ($info['group'] == $group) {
1800                $keys[] = $key;
1801            }
1802        }
1803
1804        return $keys;
1805    }
1806
1807    /**
1808     * Get the list of allowed set values for a config value.  Returns
1809     * NULL for config values that are not sets.
1810     *
1811     * @param string  config key
1812     * @return array enumerated array of set values, or NULL if the
1813     *               config key is unknown or not a set
1814     *
1815     * @access public
1816     *
1817     */
1818    function getSetValues($key)
1819    {
1820        if (isset($this->configuration_info[$key]) &&
1821            isset($this->configuration_info[$key]['type']) &&
1822            $this->configuration_info[$key]['type'] == 'set')
1823        {
1824            $valid_set = $this->configuration_info[$key]['valid_set'];
1825            reset($valid_set);
1826            if (key($valid_set) === 0) {
1827                return $valid_set;
1828            }
1829
1830            return array_keys($valid_set);
1831        }
1832
1833        return null;
1834    }
1835
1836    /**
1837     * Get all the current config keys.
1838     *
1839     * @return array simple array of config keys
1840     *
1841     * @access public
1842     */
1843    function getKeys()
1844    {
1845        $keys = array();
1846        foreach ($this->layers as $layer) {
1847            $test = $this->configuration[$layer];
1848            if (isset($test['__channels'])) {
1849                foreach ($test['__channels'] as $channel => $configs) {
1850                    $keys = array_merge($keys, $configs);
1851                }
1852            }
1853
1854            unset($test['__channels']);
1855            $keys = array_merge($keys, $test);
1856
1857        }
1858        return array_keys($keys);
1859    }
1860
1861    /**
1862     * Remove the a config key from a specific config layer.
1863     *
1864     * @param string config key
1865     * @param string (optional) config layer
1866     * @param string (optional) channel (defaults to default channel)
1867     * @return bool TRUE on success, FALSE on failure
1868     *
1869     * @access public
1870     */
1871    function remove($key, $layer = 'user', $channel = null)
1872    {
1873        if ($channel === null) {
1874            $channel = $this->getDefaultChannel();
1875        }
1876
1877        if ($channel !== 'pear.php.net') {
1878            if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
1879                unset($this->configuration[$layer]['__channels'][$channel][$key]);
1880                return true;
1881            }
1882        }
1883
1884        if (isset($this->configuration[$layer][$key])) {
1885            unset($this->configuration[$layer][$key]);
1886            return true;
1887        }
1888
1889        return false;
1890    }
1891
1892    /**
1893     * Temporarily remove an entire config layer.  USE WITH CARE!
1894     *
1895     * @param string config key
1896     * @param string (optional) config layer
1897     * @return bool TRUE on success, FALSE on failure
1898     *
1899     * @access public
1900     */
1901    function removeLayer($layer)
1902    {
1903        if (isset($this->configuration[$layer])) {
1904            $this->configuration[$layer] = array();
1905            return true;
1906        }
1907
1908        return false;
1909    }
1910
1911    /**
1912     * Stores configuration data in a layer.
1913     *
1914     * @param string config layer to store
1915     * @return bool TRUE on success, or PEAR error on failure
1916     *
1917     * @access public
1918     */
1919    function store($layer = 'user', $data = null)
1920    {
1921        return $this->writeConfigFile(null, $layer, $data);
1922    }
1923
1924    /**
1925     * Tells what config layer that gets to define a key.
1926     *
1927     * @param string config key
1928     * @param boolean return the defining channel
1929     *
1930     * @return string|array the config layer, or an empty string if not found.
1931     *
1932     *         if $returnchannel, the return is an array array('layer' => layername,
1933     *         'channel' => channelname), or an empty string if not found
1934     *
1935     * @access public
1936     */
1937    function definedBy($key, $returnchannel = false)
1938    {
1939        foreach ($this->layers as $layer) {
1940            $channel = $this->getDefaultChannel();
1941            if ($channel !== 'pear.php.net') {
1942                if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
1943                    if ($returnchannel) {
1944                        return array('layer' => $layer, 'channel' => $channel);
1945                    }
1946                    return $layer;
1947                }
1948            }
1949
1950            if (isset($this->configuration[$layer][$key])) {
1951                if ($returnchannel) {
1952                    return array('layer' => $layer, 'channel' => 'pear.php.net');
1953                }
1954                return $layer;
1955            }
1956        }
1957
1958        return '';
1959    }
1960
1961    /**
1962     * Tells whether a given key exists as a config value.
1963     *
1964     * @param string config key
1965     * @return bool whether <config key> exists in this object
1966     *
1967     * @access public
1968     */
1969    function isDefined($key)
1970    {
1971        foreach ($this->layers as $layer) {
1972            if (isset($this->configuration[$layer][$key])) {
1973                return true;
1974            }
1975        }
1976
1977        return false;
1978    }
1979
1980    /**
1981     * Tells whether a given config layer exists.
1982     *
1983     * @param string config layer
1984     * @return bool whether <config layer> exists in this object
1985     *
1986     * @access public
1987     */
1988    function isDefinedLayer($layer)
1989    {
1990        return isset($this->configuration[$layer]);
1991    }
1992
1993    /**
1994     * Returns the layers defined (except the 'default' one)
1995     *
1996     * @return array of the defined layers
1997     */
1998    function getLayers()
1999    {
2000        $cf = $this->configuration;
2001        unset($cf['default']);
2002        return array_keys($cf);
2003    }
2004
2005    function apiVersion()
2006    {
2007        return '1.1';
2008    }
2009
2010    /**
2011     * @return PEAR_Registry
2012     */
2013    function &getRegistry($use = null)
2014    {
2015        $layer = $use === null ? 'user' : $use;
2016        if (isset($this->_registry[$layer])) {
2017            return $this->_registry[$layer];
2018        } elseif ($use === null && isset($this->_registry['system'])) {
2019            return $this->_registry['system'];
2020        } elseif ($use === null && isset($this->_registry['default'])) {
2021            return $this->_registry['default'];
2022        } elseif ($use) {
2023            $a = false;
2024            return $a;
2025        }
2026
2027        // only go here if null was passed in
2028        echo "CRITICAL ERROR: Registry could not be initialized from any value";
2029        exit(1);
2030    }
2031
2032    /**
2033     * This is to allow customization like the use of installroot
2034     * @param PEAR_Registry
2035     * @return bool
2036     */
2037    function setRegistry(&$reg, $layer = 'user')
2038    {
2039        if ($this->_noRegistry) {
2040            return false;
2041        }
2042
2043        if (!in_array($layer, array('user', 'system'))) {
2044            return false;
2045        }
2046
2047        $this->_registry[$layer] = &$reg;
2048        if (is_object($reg)) {
2049            $this->_registry[$layer]->setConfig($this, false);
2050        }
2051
2052        return true;
2053    }
2054
2055    function noRegistry()
2056    {
2057        $this->_noRegistry = true;
2058    }
2059
2060    /**
2061     * @return PEAR_REST
2062     */
2063    function &getREST($version, $options = array())
2064    {
2065        $version = str_replace('.', '', $version);
2066        if (!class_exists($class = 'PEAR_REST_' . $version)) {
2067            require_once 'PEAR/REST/' . $version . '.php';
2068        }
2069
2070        $remote = new $class($this, $options);
2071        return $remote;
2072    }
2073
2074    /**
2075     * The ftp server is set in {@link readFTPConfigFile()}.  It exists only if a
2076     * remote configuration file has been specified
2077     * @return PEAR_FTP|false
2078     */
2079    function &getFTP()
2080    {
2081        if (isset($this->_ftp)) {
2082            return $this->_ftp;
2083        }
2084
2085        $a = false;
2086        return $a;
2087    }
2088
2089    static function _prependPath($path, $prepend)
2090    {
2091        if (strlen($prepend) > 0) {
2092            if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
2093                if (preg_match('/^[a-z]:/i', $prepend)) {
2094                    $prepend = substr($prepend, 2);
2095                } elseif ($prepend{0} != '\\') {
2096                    $prepend = "\\$prepend";
2097                }
2098                $path = substr($path, 0, 2) . $prepend . substr($path, 2);
2099            } else {
2100                $path = $prepend . $path;
2101            }
2102        }
2103        return $path;
2104    }
2105
2106    /**
2107     * @param string|false installation directory to prepend to all _dir variables, or false to
2108     *                     disable
2109     */
2110    function setInstallRoot($root)
2111    {
2112        if (substr($root, -1) == DIRECTORY_SEPARATOR) {
2113            $root = substr($root, 0, -1);
2114        }
2115        $old = $this->_installRoot;
2116        $this->_installRoot = $root;
2117        if (($old != $root) && !$this->_noRegistry) {
2118            foreach (array_keys($this->_registry) as $layer) {
2119                if ($layer == 'ftp' || !isset($this->_registry[$layer])) {
2120                    continue;
2121                }
2122                $this->_registry[$layer] =
2123                    new PEAR_Registry(
2124                        $this->get('php_dir', $layer, 'pear.php.net'), false, false,
2125                        $this->get('metadata_dir', $layer, 'pear.php.net'));
2126                $this->_registry[$layer]->setConfig($this, false);
2127                $this->_regInitialized[$layer] = false;
2128            }
2129        }
2130    }
2131}
2132