Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * A package to make adding self updating functionality to other
4
 * packages easy.
5
 *
6
 * PHP versions 4 and 5
7
 *
8
 * LICENSE: This source file is subject to version 3.01 of the PHP license
9
 * that is available through the world-wide-web at the following URI:
10
 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
11
 * the PHP License and are unable to obtain it through the web, please
12
 * send a note to license@php.net so we can mail you a copy immediately.
13
 *
14
 * @category   PEAR
15
 * @package    PEAR_PackageUpdate
16
 * @author     Scott Mattocks
17
 * @copyright  2006 Scott Mattocks
18
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
19
 * @version    CVS: $Id: PackageUpdate.php,v 1.1 2008/02/02 15:09:40 farell Exp $
20
 * @link       http://pear.php.net/package/PEAR_PackageUpdate
21
 * @since      File available since Release 0.4.0a1
22
 */
23
 
24
require_once 'PEAR/ErrorStack.php';
25
require_once 'PEAR/Config.php';
26
 
27
// Constants for preferences.
28
define('PEAR_PACKAGEUPDATE_PREF_NOUPDATES',   0);
29
define('PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE', 1);
30
define('PEAR_PACKAGEUPDATE_PREF_TYPE',        2);
31
define('PEAR_PACKAGEUPDATE_PREF_STATE',       3);
32
 
33
// Constants for states.
34
define('PEAR_PACKAGEUPDATE_STATE_DEVEL',  'devel');
35
define('PEAR_PACKAGEUPDATE_STATE_ALPHA',  'alpha');
36
define('PEAR_PACKAGEUPDATE_STATE_BETA',   'beta');
37
define('PEAR_PACKAGEUPDATE_STATE_STABLE', 'stable');
38
 
39
// Constants for release types.
40
define('PEAR_PACKAGEUPDATE_TYPE_BUG',   'bug');
41
define('PEAR_PACKAGEUPDATE_TYPE_MINOR', 'minor');
42
define('PEAR_PACKAGEUPDATE_TYPE_MAJOR', 'major');
43
 
44
// Constants for errors.
45
define('PEAR_PACKAGEUPDATE_ERROR_NOPACKAGE',            -1);
46
define('PEAR_PACKAGEUPDATE_ERROR_NOCHANNEL',            -2);
47
define('PEAR_PACKAGEUPDATE_ERROR_NOINFO',               -3);
48
define('PEAR_PACKAGEUPDATE_ERROR_NOTINSTALLED',         -4);
49
define('PEAR_PACKAGEUPDATE_ERROR_PREFFILE_READACCESS',  -5);
50
define('PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEACCESS', -6);
51
define('PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEERROR',  -7);
52
define('PEAR_PACKAGEUPDATE_ERROR_PREFFILE_CORRUPTED',   -8);
53
define('PEAR_PACKAGEUPDATE_ERROR_INVALIDTYPE',          -9);
54
define('PEAR_PACKAGEUPDATE_ERROR_INVALIDSTATE',         -10);
55
define('PEAR_PACKAGEUPDATE_ERROR_INVALIDPREF',          -11);
56
define('PEAR_PACKAGEUPDATE_ERROR_NONEXISTENTDRIVER',    -12);
57
 
58
// Error messages.
59
$GLOBALS['_PEAR_PACKAGEUPDATE_ERRORS'] =
60
    array(
61
        PEAR_PACKAGEUPDATE_ERROR_NOPACKAGE =>
62
            'No package name provided',
63
        PEAR_PACKAGEUPDATE_ERROR_NOCHANNEL =>
64
            'No channel name provided',
65
        PEAR_PACKAGEUPDATE_ERROR_NOINFO =>
66
            'No update information is available for %packagename%.',
67
        PEAR_PACKAGEUPDATE_ERROR_NOTINSTALLED =>
68
            '%packagename% is not installed. It cannot be updated.',
69
        PEAR_PACKAGEUPDATE_ERROR_PREFFILE_READACCESS =>
70
            'Preferences cannot be read from %file%.',
71
        PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEACCESS =>
72
            'Preferences cannot be written to %file% because of access permission errors.',
73
        PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEERROR =>
74
            'An error occurred while trying to write the preferences to %file%.',
75
        PEAR_PACKAGEUPDATE_ERROR_PREFFILE_CORRUPTED =>
76
            'Preferences file is corrupted.',
77
        PEAR_PACKAGEUPDATE_ERROR_INVALIDTYPE =>
78
            'Invalid release type: %type%',
79
        PEAR_PACKAGEUPDATE_ERROR_INVALIDSTATE =>
80
            'Invalid release state: %state%',
81
        PEAR_PACKAGEUPDATE_ERROR_INVALIDPREF =>
82
            'Invalid preference: %preference%',
83
        PEAR_PACKAGEUPDATE_ERROR_NONEXISTENTDRIVER =>
84
            'Driver %drivername% could not be found.'
85
    );
86
 
87
/**
88
 * The package allows a developer to add a few lines of code to
89
 * their existing packages and have the ability to update their
90
 * package automatically. This auto-update ability allows users
91
 * to stay up to date much more easily than requiring them to
92
 * update manually.
93
 *
94
 * This package keeps track of user preferences such as "don't
95
 * remind me again".
96
 *
97
 * This package is designed to be a backend to different front
98
 * ends written for this package. For example, this package can
99
 * be used to drive a PHP-GTK 2, CLI or web front end. The API
100
 * for this package should be flexible enough to allow any type
101
 * of front end to be used and also to allow the package to be
102
 * used directly in another package without a front end driver.
103
 *
104
 * The interface for this package must allow for the following
105
 * functionality:
106
 * - check to see if a new version is available for a given
107
 *   package on a given channel
108
 *   - check minimum state
109
 * - present information regarding the upgrade (version, size)
110
 *   - inform user about dependencies
111
 * - allow user to confirm or cancel upgrade
112
 * - download and install the package
113
 * - track preferences on a per package basis
114
 *   - don't ask again
115
 *   - don't ask until next release
116
 *   - only ask for state XXXX or higher
117
 *   - bug/minor/major updates only
118
 * - update channel automatically
119
 * - force application to exit when upgrade complete
120
 *   - PHP-GTK/CLI apps must exit to allow classes to reload
121
 *   - web front end could send headers to reload certain page
122
 *
123
 * This class is simply a wrapper for PEAR classes that actually
124
 * do the work.
125
 *
126
 * EXAMPLE:
127
 * <code>
128
 * <?php
129
 *  class Goo {
130
 *      function __construct()
131
 *      {
132
 *          // Check for updates...
133
 *          require_once 'PEAR/PackageUpdate.php';
134
 *          $ppu =& PEAR_PackageUpdate::factory('Gtk2', 'Goo', 'pear');
135
 *          if ($ppu !== false) {
136
 *              if ($ppu->checkUpdate()) {
137
 *                  // Use a dialog window to ask permission to update.
138
 *                  if ($ppu->presentUpdate()) {
139
 *                      if ($ppu->update()) {
140
 *                          // If the update succeeded, the application should
141
 *                          // be restarted.
142
 *                          $ppu->forceRestart();
143
 *                      }
144
 *                  }
145
 *              }
146
 *          }
147
 *          // ...
148
 *      }
149
 *      // ...
150
 *  }
151
 * ?>
152
 * </code>
153
 *
154
 * @category   PEAR
155
 * @package    PEAR_PackageUpdate
156
 * @author     Scott Mattocks
157
 * @copyright  2006 Scott Mattocks
158
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
159
 * @version    Release: 0.5.0
160
 * @link       http://pear.php.net/package/PEAR_PackageUpdate
161
 * @since      Class available since Release 0.4.0a1
162
 */
163
 
164
class PEAR_PackageUpdate
165
{
166
    /**
167
     * The user's update preferences.
168
     *
169
     * @access public
170
     * @var    array
171
     * @since  0.4.0a1
172
     */
173
    var $preferences = array();
174
 
175
    /**
176
     * The name of the package.
177
     *
178
     * @access public
179
     * @var    string
180
     * @since  0.4.0a1
181
     */
182
    var $packageName;
183
 
184
    /**
185
     * The channel the package is hosted on.
186
     *
187
     * @access public
188
     * @var    string
189
     * @since  0.4.0a1
190
     */
191
    var $channel;
192
 
193
    /**
194
     * The latest version available for the given package.
195
     *
196
     * @access public
197
     * @var    string
198
     * @since  0.4.0a1
199
     */
200
    var $latestVersion;
201
 
202
    /**
203
     * Information about the latest version of the package.
204
     *
205
     * @access public
206
     * @var    array
207
     * @since  0.4.0a1
208
     */
209
    var $info = array();
210
 
211
    /**
212
     * The current installed version of the package.
213
     *
214
     * @access public
215
     * @var    string
216
     * @since  0.4.0a1
217
     */
218
    var $instVersion;
219
 
220
    /**
221
     * A collection of errors that have occurred.
222
     *
223
     * @access public
224
     * @var    object
225
     * @since  0.4.0a1
226
     */
227
    var $errors;
228
 
229
    /**
230
     * PHP 4 style constructor. Calls the PHP 5 style constructor.
231
     *
232
     * @access public
233
     * @param  string $packageName The package to update.
234
     * @param  string $channel     The channel the package resides on.
235
     * @return void
236
     * @since  0.4.0a1
237
     */
238
    function PEAR_PackageUpdate($packageName, $channel)
239
    {
240
        PEAR_PackageUpdate::__construct($packageName, $channel);
241
    }
242
 
243
    /**
244
     * PHP 5 style constructor. Loads the user preferences.
245
     *
246
     * @access public
247
     * @param  string $packageName The package to update.
248
     * @param  string $channel     The channel the package resides on.
249
     * @return void
250
     * @since  0.4.0a1
251
     */
252
    function __construct($packageName, $channel)
253
    {
254
        // Create a pear error stack.
255
        $this->errors =& PEAR_ErrorStack::singleton(get_class($this));
256
        $this->errors->setContextCallback(array(&$this, '_getBacktrace'));
257
 
258
        // Set the package name and channel.
259
        $this->packageName = $packageName;
260
        $this->channel     = $channel;
261
 
262
        // Load the user's preferences.
263
        $this->loadPreferences();
264
    }
265
 
266
    /**
267
     * Callback that generates context information (location of error)
268
     * for the package error stack.
269
     *
270
     * @access private
271
     * @return mixed  backtrace context array or false is unavailable
272
     * @since  0.4.3
273
     */
274
    function _getBacktrace()
275
    {
276
        if (function_exists('debug_backtrace')) {
277
            $backtrace = debug_backtrace();
278
            $backtrace = $backtrace[count($backtrace)-1];
279
        } else {
280
            $backtrace = false;
281
        }
282
        return $backtrace;
283
    }
284
 
285
    /**
286
     * Creates an instance of the given update class.
287
     *
288
     * @access public
289
     * @param  string $driver The type of PPU to create.
290
     * @param  string $packageName The package to update.
291
     * @param  string $channel     The channel the package resides on.
292
     * @return object An instance of type PEAR_PackageUpdate_$driver
293
     * @since  0.4.0a1
294
     * @throws PEAR_PACKAGEUPDATE_ERROR_NONEXISTENTDRIVER
295
     */
296
    function &factory($driver, $packageName, $channel)
297
    {
298
        $class = 'PEAR_PackageUpdate_' . $driver;
299
 
300
        // Attempt to include a custom version of the named class, but don't treat
301
        // a failure as fatal.  The caller may have already included their own
302
        // version of the named class.
303
        if (!class_exists($class)) {
304
 
305
            // Try to include the driver.
306
            $file = 'PEAR/PackageUpdate/' . $driver . '.php';
307
 
308
            if (!PEAR_PackageUpdate::isIncludable($file)) {
309
                PEAR_ErrorStack::staticPush('PEAR_PackageUpdate',
310
                                            PEAR_PACKAGEUPDATE_ERROR_NONEXISTENTDRIVER,
311
                                            null,
312
                                            array('drivername' => $driver),
313
                                            $GLOBALS['_PEAR_PACKAGEUPDATE_ERRORS'][PEAR_PACKAGEUPDATE_ERROR_NONEXISTENTDRIVER]
314
                                            );
315
                // Must assign a variable to avoid notice about references.
316
                $false = false;
317
                return $false;
318
            }
319
            include_once $file;
320
        }
321
 
322
        // See if the class exists now.
323
        if (!class_exists($class)) {
324
            // Must assign a variable to avoid notice about references.
325
            $false = false;
326
            return $false;
327
        }
328
 
329
        // Try to instantiate the class.
330
        $instance =& new $class($packageName, $channel);
331
        return $instance;
332
    }
333
 
334
    /**
335
     * Returns whether or not a path is in the include path.
336
     *
337
     * @static
338
     * @access public
339
     * @param  string  $path
340
     * @return boolean true if the path is in the include path.
341
     * @since  0.4.2
342
     */
343
    function isIncludable($path)
344
    {
345
        // Break up the include path and check to see if the path is readable.
346
        foreach (explode(PATH_SEPARATOR, get_include_path()) as $ip) {
347
            if (file_exists($ip . DIRECTORY_SEPARATOR . $path) &&
348
                is_readable($ip . DIRECTORY_SEPARATOR . $path)
349
                ) {
350
                return true;
351
            }
352
        }
353
 
354
        // If we got down here, the path is not readable from the include path.
355
        return false;
356
    }
357
 
358
    /**
359
     * Loads the user's preferences from a file.
360
     *
361
     * The preferences are stored in the user's home directory
362
     * as the file .ppurc. The file contains a serialized array
363
     * of preferences for each package that has been checked for
364
     * updates so far.
365
     *
366
     * @access protected
367
     * @return boolean   true on success, false on error
368
     * @since  0.4.0a1
369
     * @throws PEAR_PACKAGEUPDATE_ERROR_PREFFILE_READACCESS,
370
     *         PEAR_PACKAGEUPDATE_ERROR_PREFFILE_CORRUPTED
371
     */
372
    function loadPreferences()
373
    {
374
        // Get the preferences file.
375
        $prefFile = $this->determinePrefFile();
376
 
377
        // Make sure the prefFile exists.
378
        if (!@file_exists($prefFile)) {
379
            // Try to create it by saving the current preferences (probably
380
            // just an empty array).
381
            $this->savePreferences();
382
        }
383
 
384
        // Make sure the prefFile is readable.
385
        if (!@is_readable($prefFile)) {
386
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_PREFFILE_READACCESS,
387
                             NULL, array('file' => $prefFile)
388
                             );
389
            return false;
390
        }
391
 
392
        // Get the contents of the prefFile.
393
        $contents = file_get_contents($prefFile);
394
 
395
        // Unserialize the data.
396
        $preferences = unserialize($contents);
397
 
398
        // Make sure the contents were unserialized properly.
399
        // Borrowed from PEAR_Config.
400
        if (!$preferences && strlen($contents) > 7) {
401
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_PREFFILE_CORRUPTED);
402
            return false;
403
        }
404
 
405
        $this->preferences = $preferences;
406
 
407
        return true;
408
    }
409
 
410
    /**
411
     * Returns the path to the preferences file.
412
     *
413
     * @access protected
414
     * @return string
415
     * @since  0.4.0a1
416
     */
417
    function determinePrefFile()
418
    {
419
        // Determine the preferences file.
420
        // Borrowed from PEAR_Config
421
 
422
        $ds = DIRECTORY_SEPARATOR;
423
        if (OS_WINDOWS) {
424
            $prefFile = PEAR_CONFIG_SYSCONFDIR . $ds . 'ppurc.ini';
425
        } else {
426
            $prefFile = getenv('HOME') . $ds . '.ppurc';
427
        }
428
 
429
        return $prefFile;
430
    }
431
 
432
    /**
433
     * Checks to see if an update is available.
434
     *
435
     * Respects the user preferences when determining if an
436
     * update is avaiable. Returns true if an update is available
437
     * and the user may want to update the package.
438
     *
439
     * @access public
440
     * @return boolean true if an update is available.
441
     * @since  0.4.0a1
442
     */
443
    function checkUpdate()
444
    {
445
        // Check to see if an update is available.
446
        if (empty($this->latestVersion) || empty ($this->info)) {
447
            if (!$this->getPackageInfo()) {
448
                return false;
449
            }
450
        }
451
 
452
        // See if the installed version is older than the current version.
453
        if (version_compare($this->latestVersion, $this->instVersion, '>')) {
454
            // Check to see if the user's preferences allow an update.
455
            if (!$this->preferencesAllowUpdate()) {
456
                // User doesn't want to update to the latest version.
457
                return false;
458
            }
459
            return true;
460
        } else {
461
            return false;
462
        }
463
    }
464
 
465
    /**
466
     * Returns the latest information about the given package.
467
     *
468
     * @access protected
469
     * @return boolean   true on success, false on error
470
     * @since  0.4.0a1
471
     * @throws PEAR_PACKAGEUPDATE_ERROR_NOPACKAGE,
472
     *         PEAR_PACKAGEUPDATE_ERROR_NOCHANNEL,
473
     *         PEAR_PACKAGEUPDATE_ERROR_NOINFO
474
     */
475
    function getPackageInfo()
476
    {
477
        // Only check once.
478
        if (isset($this->latestVersion) && isset($this->info)) {
479
            return true;
480
        }
481
 
482
        // Make sure the channel and package are set.
483
        if (empty($this->packageName)) {
484
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOPACKAGE);
485
            return false;
486
        }
487
 
488
        if (empty($this->channel)) {
489
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOCHANNEL);
490
            return false;
491
        }
492
 
493
        // Create a config object.
494
        $config = new PEAR_Config();
495
 
496
        // Get the config's registry object.
497
        $reg = $config->getRegistry();
498
 
499
        // Parse the package name.
500
        $parsed = $reg->parsePackageName($this->channel . '/' . $this->packageName);
501
 
502
        // Check for errors.
503
        if (PEAR::isError($parsed)) {
504
            $this->pushError($parsed);
505
            return false;
506
        }
507
 
508
        // Get a PEAR_Remote instance.
509
        $r = $config->getRemote();
510
 
511
        // Get the package info.
512
        $info = $r->call('package.info', $parsed['package']);
513
 
514
        // Check to make sure the package was found.
515
        if (PEAR::isError($info)) {
516
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOINFO, NULL,
517
                             array('packagename' => $this->packageName)
518
                             );
519
            return false;
520
        }
521
 
522
        // Get the installed version of the package.
523
        $this->instVersion = $reg->packageInfo($parsed['package'],
524
                                               'version',
525
                                               $parsed['channel']
526
                                               );
527
 
528
        // If the package is not installed, create a dummy version.
529
        if (empty($this->instVersion)) {
530
            $this->instVersion = '0.0.0';
531
        }
532
 
533
        // Pull out the latest information.
534
        $this->latestVersion = reset(array_keys($info['releases']));
535
        $this->info          = reset($info['releases']);
536
 
537
        return true;
538
    }
539
 
540
    /**
541
     * Returns the preferences associated with the given package.
542
     *
543
     * The preferences returned are an array with the folling values:
544
     * - don't ask again
545
     * - don't ask until next version
546
     * - only ask for state x
547
     * - bug/minor/major updates only
548
     *
549
     * @access public
550
     * @return array
551
     * @since  0.4.0a1
552
     */
553
    function getPackagePreferences()
554
    {
555
        if (isset($this->preferences[$this->channel][$this->packageName])) {
556
            return $this->preferences[$this->channel][$this->packageName];
557
        } else {
558
            return array();
559
        }
560
    }
561
 
562
    /**
563
     * Saves the current prefernces to the RC file.
564
     *
565
     * @access public
566
     * @return boolean true on success, false on error
567
     * @since  0.4.0a1
568
     * @throws PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEACCESS,
569
     *         PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEERROR
570
     */
571
    function savePreferences()
572
    {
573
        // Get the file to save the preferences to.
574
        $prefFile = $this->determinePrefFile();
575
 
576
        // Open the file for writing.
577
        $fp = fopen($prefFile, 'w');
578
        if ($fp === false) {
579
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEACCESS,
580
                             NULL, array('file' => $prefFile)
581
                             );
582
            return false;
583
        }
584
 
585
        // Serialize the contents.
586
        $serialCont = serialize($this->preferences);
587
 
588
        // Write the contents to the file.
589
        if (fwrite($fp, $serialCont) === false) {
590
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEERROR,
591
                             NULL, array('file' => $prefFile)
592
                             );
593
            return false;
594
        }
595
 
596
        // Close the file.
597
        if (!fclose($fp)) {
598
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_PREFFILE_WRITEERROR,
599
                             NULL, array('file' => $prefFile)
600
                             );
601
            return false;
602
        } else {
603
            return true;
604
        }
605
    }
606
 
607
    /**
608
     * Returns whether or not the user's preferences will allow an update to
609
     * take place.
610
     *
611
     * The user's preferences may define restrictions such as:
612
     *   - don't update
613
     *   - don't ask until next version (remembers last version asked)
614
     *   - only ask for state XXXX or higher
615
     *   - minor or higher (no bug fix)
616
     *   - major only
617
     *
618
     * @access public
619
     * @return boolean true if the preferences will allow an update for the
620
     *                 latest version.
621
     * @since  0.4.0a1
622
     */
623
    function preferencesAllowUpdate()
624
    {
625
        // Get the preferences for the package.
626
        $prefs = $this->getPackagePreferences();
627
 
628
        // Check to see if the user wants to be notified about any updates for
629
        // this package.
630
        if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_NOUPDATES]) &&
631
            $prefs[PEAR_PACKAGEUPDATE_PREF_NOUPDATES]
632
            ) {
633
            return false;
634
        }
635
 
636
        // Check to see if the user has requested not to be asked until a new
637
        // version is released.
638
        if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE]) &&
639
            !version_compare($this->latestVersion,
640
                             $prefs[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE],
641
                             '>')
642
            ) {
643
            return false;
644
        }
645
 
646
        // Check to see if the user has requested not to be asked about the
647
        // state of the latest release.
648
        // Create an array of states.
649
        $states = array(PEAR_PACKAGEUPDATE_STATE_DEVEL  => 0,
650
                        PEAR_PACKAGEUPDATE_STATE_ALPHA  => 1,
651
                        PEAR_PACKAGEUPDATE_STATE_BETA   => 2,
652
                        PEAR_PACKAGEUPDATE_STATE_STABLE => 3
653
                        );
654
        if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_STATE]) &&
655
            $states[$prefs[PEAR_PACKAGEUPDATE_PREF_STATE]] > $states[$this->info['state']]
656
            ) {
657
            return false;
658
        }
659
 
660
        // Check to see if the user only wants to be asked about a certain
661
        // type of release (minor|major).
662
        // Create an array for the types of releases.
663
        $releases = array(PEAR_PACKAGEUPDATE_TYPE_BUG   => 0,
664
                          PEAR_PACKAGEUPDATE_TYPE_MINOR => 1,
665
                          PEAR_PACKAGEUPDATE_TYPE_MAJOR => 2
666
                          );
667
        if (isset($prefs[PEAR_PACKAGEUPDATE_PREF_TYPE]) &&
668
            $releases[$prefs[PEAR_PACKAGEUPDATE_PREF_TYPE]] > $releases[$this->releaseType()]
669
            ) {
670
            return false;
671
        }
672
 
673
        // If we got down here, either there are no preferences for this
674
        // package or everything checked out.
675
        return true;
676
    }
677
 
678
    /**
679
     * Returns the type of release. (bug|minor|major);
680
     *
681
     * @access protected
682
     * @return string
683
     * @since  0.4.0a1
684
     */
685
    function releaseType()
686
    {
687
        // Break the two versions into pieces.
688
        $latest  = explode('.', $this->latestVersion);
689
        $current = explode('.', $this->instVersion);
690
 
691
        if ($latest[0] > $current[0]) {
692
            $type = PEAR_PACKAGEUPDATE_TYPE_MAJOR;
693
        } elseif ($latest[1] > $current[1]) {
694
            $type = PEAR_PACKAGEUPDATE_TYPE_MINOR;
695
        } else {
696
            $type = PEAR_PACKAGEUPDATE_TYPE_BUG;
697
        }
698
 
699
        return $type;
700
    }
701
 
702
    /**
703
     * Sets the user's preference for asking about all updates for this
704
     * package.
705
     *
706
     * @access public
707
     * @param  boolean $dontAsk
708
     * @return boolean true on success, false on failure
709
     * @since  0.4.0a1
710
     */
711
    function setDontAskAgain($dontAsk)
712
    {
713
        // Make sure the value is a boolean.
714
        settype($dontAsk, 'boolean');
715
 
716
        // Set the preference.
717
        return $this->setPreference(PEAR_PACKAGEUPDATE_PREF_NOUPDATES, $dontAsk);
718
    }
719
 
720
    /**
721
     * Sets the user's preference for asking about updates again until the next
722
     * release.
723
     *
724
     * @access public
725
     * @param  boolean $nextrelease
726
     * @return boolean true on success, false on failure
727
     * @since  0.4.0a1
728
     * @throws PEAR_PACKAGEUPDATE_ERROR_NOINFO
729
     */
730
    function setDontAskUntilNextRelease($nextrelease)
731
    {
732
        // If nextrelease is true, we have to swap its value out with the
733
        // latest version.
734
        if ($nextrelease) {
735
            // Make sure that the package info was found.
736
            if (empty($this->latestVersion)) {
737
                $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOINFO);
738
                return false;
739
            }
740
            $nextrelease = $this->latestVersion;
741
        }
742
 
743
        // Set the preference.
744
        return $this->setPreference(PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE,
745
                                    $nextrelease);
746
    }
747
 
748
    /**
749
     * Sets the user's preference for asking about release types.
750
     *
751
     * @access public
752
     * @param  string  $minType The minimum release type to allow.
753
     * @return boolean true on success, false on failure
754
     * @since  0.4.0a1
755
     * @throws PEAR_PACKAGEUPDATE_ERROR_INVALIDTYPE
756
     */
757
    function setMinimumReleaseType($minType)
758
    {
759
        // Make sure the type is acceptable.
760
        if ($minType != PEAR_PACKAGEUPDATE_TYPE_BUG   &&
761
            $minType != PEAR_PACKAGEUPDATE_TYPE_MINOR &&
762
            $minType != PEAR_PACKAGEUPDATE_TYPE_MAJOR
763
            ) {
764
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_INVALIDTYPE, NULL,
765
                             array('type' => $minType)
766
                             );
767
            return false;
768
        }
769
 
770
        // Set the preference.
771
        return $this->setPreference(PEAR_PACKAGEUPDATE_PREF_TYPE, $minType);
772
    }
773
 
774
    /**
775
     * Sets the user's preference for asking about release states.
776
     *
777
     * @access public
778
     * @param  string  $minState The minimum release state to allow.
779
     * @return boolean true on success, false on failure
780
     * @since  0.4.0a1
781
     * @throws PEAR_PACKAGEUPDATE_ERROR_INVALIDSTATE
782
     */
783
    function setMinimumState($minState)
784
    {
785
        // Make sure the type is acceptable.
786
        if ($minState != PEAR_PACKAGEUPDATE_STATE_DEVEL  &&
787
            $minState != PEAR_PACKAGEUPDATE_STATE_ALPHA  &&
788
            $minState != PEAR_PACKAGEUPDATE_STATE_BETA   &&
789
            $minState != PEAR_PACKAGEUPDATE_STATE_STABLE
790
            ) {
791
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_INVALIDSTATE, NULL,
792
                             array('state' => $minState)
793
                             );
794
            return false;
795
        }
796
 
797
        // Set the preference.
798
        return $this->setPreference(PEAR_PACKAGEUPDATE_PREF_STATE, $minState);
799
    }
800
 
801
    /**
802
     * Sets the given preference to the given value.
803
     *
804
     * Don't take any chances. Anytime a preference is set, the preferences are
805
     * saved. We can't rely on the developer to call savePreferences.
806
     *
807
     * @access protected
808
     * @param  integer   $pref  One of the preference constants.
809
     * @param  mixed     $value The value of the preference.
810
     * @return boolean   true if the preference was set and saved properly.
811
     * @since  0.4.0a1
812
     * @throws PEAR_PACKAGEUPDATE_ERROR_INVALIDPREF
813
     */
814
    function setPreference($pref, $value)
815
    {
816
        // Make sure the preference is valid.
817
        if ($pref != PEAR_PACKAGEUPDATE_PREF_NOUPDATES   &&
818
            $pref != PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE &&
819
            $pref != PEAR_PACKAGEUPDATE_PREF_TYPE        &&
820
            $pref != PEAR_PACKAGEUPDATE_PREF_STATE
821
            ) {
822
            // Invalid preference!
823
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_INVALIDPREF, NULL,
824
                             array('preference' => $pref)
825
                             );
826
            return false;
827
        }
828
 
829
        // Make sure the preferences for the channel exist.
830
        if (!isset($this->preferences[$this->channel])) {
831
            $this->preferences[$this->channel] = array();
832
        }
833
 
834
        // Make sure the preferences for the package exist.
835
        if (!isset($this->preferences[$this->channel][$this->packageName])) {
836
            $this->preferences[$this->channel][$this->packageName] = array();
837
        }
838
 
839
        // Set the preference value.
840
        $this->preferences[$this->channel][$this->packageName][$pref] = $value;
841
 
842
        // Save the preferences.
843
        return $this->savePreferences();
844
    }
845
 
846
    /**
847
     * Sets all preferences at once.
848
     *
849
     * @access public
850
     * @param  array   $preferences
851
     * @return boolean true if the preferences were set and saved.
852
     * @since  0.4.0a1
853
     */
854
    function setPreferences($preferences)
855
    {
856
        // Make sure preferences is an array.
857
        settype($preferences, 'array');
858
 
859
        // Make sure there is only valid preference information.
860
        foreach ($preferences as $pref => $value) {
861
            if ($pref != PEAR_PACKAGEUPDATE_PREF_NOUPDATES   &&
862
                $pref != PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE &&
863
                $pref != PEAR_PACKAGEUPDATE_PREF_TYPE        &&
864
                $pref != PEAR_PACKAGEUPDATE_PREF_STATE
865
                ) {
866
                unset($preferences[$pref]);
867
            }
868
        }
869
 
870
        // Make sure that next release has the latest release.
871
        if ($preferences[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE]) {
872
            $preferences[PEAR_PACKAGEUPDATE_PREF_NEXTRELEASE] = $this->latestVersion;
873
        }
874
 
875
        // Set the preferences.
876
        $this->preferences[$this->channel][$this->packageName] = $preferences;
877
 
878
        // Save the preferences.
879
        return $this->savePreferences();
880
    }
881
 
882
    /**
883
     * Updates the source for the package.
884
     *
885
     * We have to update required dependencies automatically to make sure that
886
     * everything still works properly.
887
     *
888
     * It is the developers responsibility to make sure the user is given the
889
     * option to update any optional dependencies if needed. This can be done
890
     * by creating a new instance of PEAR_PackageUpdate for the optional
891
     * dependency.
892
     *
893
     * @access public
894
     * @return boolean true if the update was successful.
895
     * @since  0.4.0a1
896
     * @throws PEAR_PACKAGEUPDATE_ERROR_NOTINSTALLED
897
     */
898
    function update()
899
    {
900
        // Create a config object.
901
        $config  = new PEAR_Config();
902
 
903
        // Change the verbosity but keep track of the value to reset it just in
904
        // case this does something permanent.
905
        $verbose = $config->get('verbose');
906
        $config->set('verbose', 0);
907
 
908
        // Create a command object to do the upgrade.
909
        // If the current version is 0.0.0 don't upgrade. That would be a
910
        // sneaky way for devs to install packages without the use knowing.
911
        if ($this->instVersion == '0.0.0') {
912
            $this->pushError(PEAR_PACKAGEUPDATE_ERROR_NOTINSTALLED, NULL,
913
                             array('packagename' => $this->packageName)
914
                             );
915
            return false;
916
        }
917
 
918
        require_once 'PEAR/Command.php';
919
        $upgrade = PEAR_Command::factory('upgrade', $config);
920
 
921
        // Try to upgrade the application.
922
        $channelPackage = $this->channel . '/' . $this->packageName;
923
        $result = $upgrade->doInstall('upgrade',
924
                                      array('onlyreqdeps' => true),
925
                                      array($channelPackage));
926
 
927
        // Reset the verbose level just to be safe.
928
        $config->set('verbose', $verbose);
929
 
930
        // Check for errors.
931
        if (PEAR::isError($result)) {
932
            $this->pushError($result);
933
            return false;
934
        } else {
935
            return true;
936
        }
937
    }
938
 
939
    /**
940
     * Redirects or exits to force the user to restart the application.
941
     *
942
     * @abstract
943
     * @access public
944
     * @return void
945
     * @since  0.4.0a1
946
     */
947
    function forceRestart()
948
    {
949
        $this->pushError('forceRestart is an abstract method that must be' .
950
                         ' overridden in the child class.');
951
    }
952
 
953
    /**
954
     * Presents the user with the option to update.
955
     *
956
     * @abstract
957
     * @access public
958
     * @return boolean true if the user wants to update
959
     * @since  0.4.0a1
960
     */
961
    function presentUpdate()
962
    {
963
        $this->pushError('presentUpdate is an abstract method that must be' .
964
                         ' overridden in the child class.');
965
 
966
        // Return false just in case something odd has happened.
967
        return false;
968
    }
969
 
970
    /**
971
     * Pushes an error onto an error stack.
972
     *
973
     * This method is just for collecting errors that occur while checking for
974
     * updates and updating a package. The child class is responsible for
975
     * displaying all errors and handling them properly. This is because the
976
     * way errors are handled varies greatly depending on the driver used.
977
     *
978
     * @access public
979
     * @param int    $code      Package-specific error code
980
     * @param string $level     Error level.  This is NOT spell-checked
981
     * @param array  $params    associative array of error parameters
982
     * @param string $msg       Error message, or a portion of it if the
983
     *                          message is to be generated
984
     * @param array  $repackage If this error re-packages an error pushed by
985
     *                          another package, place the array returned from
986
     *                          {@link pop()} in this parameter
987
     * @param array  $backtrace Protected parameter: use this to pass in the
988
     *                          {@link debug_backtrace()} that should be used
989
     *                          to find error context
990
     * @return PEAR_Error|array|Exception
991
     *                          if compatibility mode is on, a PEAR_Error is
992
     *                          also thrown.  If the class Exception exists,
993
     *                          then one is returned.
994
     * @since  0.4.0a1
995
     */
996
    function pushError($code, $level = 'error', $params = array(),
997
                        $msg = false, $repackage = false, $backtrace = false)
998
    {
999
        // Check to see if a PEAR_Error was pushed.
1000
        if (PEAR::isError($code)) {
1001
            return $this->repackagePEAR_Error($code);
1002
        }
1003
 
1004
        // Check the arguments to see if just a code was submitted.
1005
        if (is_int($code) &&
1006
            !(bool) $msg &&
1007
            isset($GLOBALS['_PEAR_PACKAGEUPDATE_ERRORS'][$code])
1008
            ) {
1009
            $msg = $GLOBALS['_PEAR_PACKAGEUPDATE_ERRORS'][$code];
1010
        }
1011
 
1012
        // Append the error onto the stack.
1013
        return $this->errors->push($code, null, $params, $msg,
1014
                                   $repackage, $backtrace
1015
                                   );
1016
    }
1017
 
1018
    /**
1019
     * Repackages PEAR_Errors for use with ErrorStack.
1020
     *
1021
     * @author Ian Eure
1022
     *
1023
     * @access public
1024
     * @param  object $error A PEAR_Error
1025
     * @return mixed  The return from PEAR::ErrorStack::push()
1026
     * @since  0.4.0a1
1027
     */
1028
    function repackagePEAR_Error(&$error)
1029
    {
1030
        static $map;
1031
        if (!isset($map)) {
1032
            $map = array(
1033
                         E_ERROR            => 'error',
1034
                         E_WARNING          => 'warning',
1035
                         E_PARSE            => 'exception',
1036
                         E_NOTICE           => 'notice',
1037
                         E_CORE_ERROR       => 'error',
1038
                         E_CORE_WARNING     => 'warning',
1039
                         E_COMPILE_ERROR    => 'exception',
1040
                         E_COMPILE_WARNING  => 'warning',
1041
                         E_USER_ERROR       => 'error',
1042
                         E_USER_WARNING     => 'warning',
1043
                         E_USER_NOTICE      => 'notice'
1044
                         );
1045
        }
1046
 
1047
        // Strip this function from the trace
1048
        if (is_array($error->backtrace)) {
1049
            array_shift($error->backtrace);
1050
            $error->userinfo['backtrace'] =& $error->backtrace;
1051
        }
1052
 
1053
        return $this->errors->push($error->code, $map[$error->level],
1054
                                   $error->userinfo, $error->message, false,
1055
                                   $error->backtrace
1056
                                   );
1057
    }
1058
 
1059
    /**
1060
     * Pops an error off the error stack.
1061
     *
1062
     * This method is just for collecting errors that occur while checking for
1063
     * updates and updating a package. The child class is responsible for
1064
     * displaying all errors and handling them properly. This is because the
1065
     * way errors are handled varies greatly depending on the driver used.
1066
     *
1067
     * @access public
1068
     * @return object A PEAR_Error instance or false if no errors exist.
1069
     * @since  0.4.0a1
1070
     */
1071
    function popError()
1072
    {
1073
        return $this->errors->pop();
1074
    }
1075
 
1076
    /**
1077
     * Returns whether or not errors have occurred (and been captured).
1078
     *
1079
     * @access public
1080
     * @return boolean
1081
     * @since  0.4.0a1
1082
     */
1083
    function hasErrors()
1084
    {
1085
        return $this->errors->hasErrors();
1086
    }
1087
}
1088
/*
1089
 * Local variables:
1090
 * tab-width: 4
1091
 * c-basic-offset: 4
1092
 * End:
1093
 */
1094
?>