1<?php
2/**
3 * +-----------------------------------------------------------------------+
4 * | Copyright (c) 2004, Tony Bibbs                                        |
5 * | All rights reserved.                                                  |
6 * |                                                                       |
7 * | Redistribution and use in source and binary forms, with or without    |
8 * | modification, are permitted provided that the following conditions    |
9 * | are met:                                                              |
10 * |                                                                       |
11 * | o Redistributions of source code must retain the above copyright      |
12 * |   notice, this list of conditions and the following disclaimer.       |
13 * | o Redistributions in binary form must reproduce the above copyright   |
14 * |   notice, this list of conditions and the following disclaimer in the |
15 * |   documentation and/or other materials provided with the distribution.|
16 * | o The names of the authors may not be used to endorse or promote      |
17 * |   products derived from this software without specific prior written  |
18 * |   permission.                                                         |
19 * |                                                                       |
20 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
21 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
22 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
23 * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
24 * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
25 * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
26 * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
27 * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
28 * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
29 * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
30 * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
31 * |                                                                       |
32 * +-----------------------------------------------------------------------+
33 * | Author: Tony Bibbs <tony@geeklog.net>                                 |
34 * +-----------------------------------------------------------------------+
35 *
36 * PHP version 5
37 *
38 * @category HTTP
39 * @package  HTTP_Session2
40 * @author   Alexander Radivaniovich <info@wwwlab.net>
41 * @author   Tony Bibbs <tony@geeklog.net>
42 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
43 * @version  CVS: $Id: Session2.php 267739 2008-10-25 16:54:23Z till $
44 * @link     http://pear.php.net/package/HTTP_Session2
45 */
46
47/**
48 * HTTP_Session2_Exception
49 */
50require_once 'HTTP/Session2/Exception.php';
51
52/**
53 * Class for managing HTTP sessions
54 *
55 * Provides access to session-state values as well as session-level
56 * settings and lifetime management methods.
57 * Based on the standart PHP session handling mechanism
58 * it provides for you more advanced features such as
59 * database container, idle and expire timeouts, etc.
60 *
61 * Expample 1:
62 *
63 * <code>
64 * // Setting some options and detecting of a new session
65 * HTTP_Session2::useCookies(false);
66 * HTTP_Session2::start('MySessionID');
67 * HTTP_Session2::set('variable', 'The string');
68 * if (HTTP_Session2::isNew()) {
69 *     echo 'new session was created with the current request';
70 *     $visitors++; // Increase visitors count
71 * }
72 *
73 * //HTTP_Session2::regenerateId();
74 * </code>
75 *
76 * Example 2:
77 *
78 * <code>
79 * // Using database container
80 * HTTP_Session2::setContainer('DB');
81 * HTTP_Session2::start();
82 * </code>
83 *
84 * Example 3:
85 *
86 * <code>
87 * // Setting timeouts
88 * HTTP_Session2::start();
89 * HTTP_Session2::setExpire(time() + 60 * 60); // expires in one hour
90 * HTTP_Session2::setIdle(10 * 60);            // idles in ten minutes
91 * if (HTTP_Session2::isExpired()) {
92 *     // expired
93 *     echo('Your session is expired!');
94 *     HTTP_Session2::destroy();
95 * }
96 * if (HTTP_Session2::isIdle()) {
97 *     // idle
98 *     echo('You've been idle for too long!');
99 *     HTTP_Session2::destroy();
100 * }
101 * HTTP_Session2::updateIdle();
102 * </code>
103 *
104 * @category HTTP
105 * @package  HTTP_Session2
106 * @author   Alexander Radivaniovich <info@wwwlab.net>
107 * @author   Tony Bibbs <tony@geeklog.net>
108 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
109 * @version  Release: @package_version@
110 * @link     http://pear.php.net/package/HTTP_Session2
111 */
112class HTTP_Session2
113{
114    /**
115     * @const STARTED - The session was started with the current request
116     */
117    const STARTED = 1;
118
119    /**
120     * @const CONTINUE - No new session was started with the current request
121     */
122    const CONTINUED = 2;
123
124    /**
125     * @const ERR_UNKNOWN_CONTAINER - Container not found.
126     */
127    const ERR_UNKNOWN_CONTAINER = 667;
128
129    /**
130     * @const ERR_SYSTEM_PERM - System permissions not sufficient.
131     *        E.g. Not enough permissions to override ini-settings.
132     */
133    const ERR_SYSTEM_PERM = 668;
134
135    /**
136     * @const ERR_SYSTEM_PRECONDITION - Precondition failed. E.g. error occured and
137     *        HTTP_Session2 can't start up, etc..
138     */
139    const ERR_SYSTEM_PRECONDITION = 669;
140
141    /**
142     * @const ERR_NOT_IMPLEMENTED Feature is not yet Implement in the container.
143     */
144    const ERR_NOT_IMPLEMENTED = 670;
145
146    /**
147     * Container instance
148     */
149    public static $container;
150
151    /**
152     * Sets user-defined session storage functions
153     *
154     * Sets the user-defined session storage functions which are used
155     * for storing and retrieving data associated with a session.
156     * This is most useful when a storage method other than
157     * those supplied by PHP sessions is preferred.
158     * i.e. Storing the session data in a local database.
159     *
160     * @param string $container         Name of the container (e.g. DB, MDB, ...).
161     * @param array  $container_options Options, most likely an array.
162     *
163     * @return void
164     * @see session_set_save_handler()
165     */
166    static function setContainer($container, $container_options = null)
167    {
168        $container_class     = 'HTTP_Session2_Container_' . $container;
169        $container_classfile = 'HTTP/Session2/Container/' . $container . '.php';
170
171        if (!class_exists($container_class)) {
172            include_once $container_classfile;
173        }
174        if (!class_exists($container_class)) {
175            throw new HTTP_Session2_Exception(
176                "Container class, $container_class, does not exist",
177                self::ERR_UNKNOWN_CONTAINER);
178        }
179        self::$container = new $container_class($container_options);
180
181        self::$container->set();
182    }
183
184    /**
185     * Initializes session data
186     *
187     * Creates a session (or resumes the current one
188     * based on the session id being passed
189     * via a GET variable or a cookie).
190     * You can provide your own name and/or id for a session.
191     *
192     * @param string $name Name of a session, default is 'SessionID'
193     * @param string $id   Id of a session which will be used
194     *                     only when the session is new
195     *
196     * @return void
197     * @see    session_name()
198     * @see    session_id()
199     * @see    session_start()
200     */
201    public static function start($name = 'SessionID', $id = null)
202    {
203        self::name($name);
204        if (is_null(self::detectID())) {
205            if ($id) {
206                self::id($id);
207            } else {
208                self::id(uniqid(dechex(rand())));
209            }
210        }
211        session_start();
212        if (!isset($_SESSION['__HTTP_Session2_Info'])) {
213            $_SESSION['__HTTP_Session2_Info'] = self::STARTED;
214        } else {
215            $_SESSION['__HTTP_Session2_Info'] = self::CONTINUED;
216        }
217    }
218
219    /**
220     * Writes session data and ends session
221     *
222     * Session data is usually stored after your script
223     * terminated without the need to call HTTP_Session2::stop(),
224     * but as session data is locked to prevent concurrent
225     * writes only one script may operate on a session at any time.
226     * When using framesets together with sessions you will
227     * experience the frames loading one by one due to this
228     * locking. You can reduce the time needed to load all the
229     * frames by ending the session as soon as all changes
230     * to session variables are done.
231     *
232     * @return void
233     * @see    session_write_close()
234     */
235    public static function pause()
236    {
237        session_write_close();
238    }
239
240    /**
241     * Frees all session variables and destroys all data
242     * registered to a session
243     *
244     * This method resets the $_SESSION variable and
245     * destroys all of the data associated
246     * with the current session in its storage (file or DB).
247     * It forces new session to be started after this method
248     * is called. It does not unset the session cookie.
249     *
250     * @return void
251     * @see    session_unset()
252     * @see    session_destroy()
253     */
254    public static function destroy()
255    {
256        session_unset();
257        session_destroy();
258    }
259
260    /**
261     * Free all session variables
262     *
263     * @todo   TODO Save expire and idle timestamps?
264     * @return void
265     */
266    public static function clear()
267    {
268        $info = $_SESSION['__HTTP_Session2_Info'];
269
270        session_unset();
271
272        $_SESSION['__HTTP_Session2_Info'] = $info;
273    }
274
275    /**
276     * Tries to find any session id in $_GET, $_POST or $_COOKIE
277     *
278     * @return string Session ID (if exists) or null
279     */
280    public static function detectID()
281    {
282        if (self::useCookies()) {
283            if (isset($_COOKIE[self::name()])) {
284                return $_COOKIE[self::name()];
285            }
286        } else {
287            if (isset($_GET[self::name()])) {
288                return $_GET[self::name()];
289            }
290            if (isset($_POST[self::name()])) {
291                return $_POST[self::name()];
292            }
293        }
294        return null;
295    }
296
297    /**
298     * Sets new name of a session
299     *
300     * @param string $name New name of a sesion
301     *
302     * @return string Previous name of a session
303     * @see    session_name()
304     */
305    public static function name($name = null)
306    {
307        if (isset($name)) {
308            return session_name($name);
309        }
310        return session_name();
311    }
312
313    /**
314     * Sets new ID of a session
315     *
316     * @param string $id New ID of a sesion
317     *
318     * @return string Previous ID of a session
319     * @see    session_id()
320     */
321    public static function id($id = null)
322    {
323        if (isset($id)) {
324            return session_id($id);
325        }
326        return session_id();
327    }
328
329    /**
330     * Sets the maximum expire time
331     *
332     * @param integer $time Time in seconds
333     * @param bool    $add  Add time to current expire time or not
334     *
335     * @return void
336     */
337    public static function setExpire($time, $add = false)
338    {
339        if ($add && isset($_SESSION['__HTTP_Session2_Expire'])) {
340            $_SESSION['__HTTP_Session2_Expire'] += $time;
341        } else {
342            $_SESSION['__HTTP_Session2_Expire'] = $time;
343        }
344        if (!isset($_SESSION['__HTTP_Session2_Expire_TS'])) {
345            $_SESSION['__HTTP_Session2_Expire_TS'] = time();
346        }
347    }
348
349    /**
350     * Sets the maximum idle time
351     *
352     * Sets the time-out period allowed
353     * between requests before the session-state
354     * provider terminates the session.
355     *
356     * @param integer $time Time in seconds
357     * @param bool    $add  Add time to current maximum idle time or not
358     *
359     * @return void
360     */
361    public static function setIdle($time, $add = false)
362    {
363        if ($add && isset($_SESSION['__HTTP_Session2_Idle'])) {
364            $_SESSION['__HTTP_Session2_Idle'] += $time;
365        } else {
366            $_SESSION['__HTTP_Session2_Idle'] = $time;
367        }
368        if (!isset($_SESSION['__HTTP_Session2_Idle_TS'])) {
369            $_SESSION['__HTTP_Session2_Idle_TS'] = time();
370        }
371    }
372
373    /**
374     * Returns the time up to the session is valid
375     *
376     * @return integer Time when the session idles
377     */
378    public static function sessionValidThru()
379    {
380        if (
381            !isset($_SESSION['__HTTP_Session2_Idle_TS'])
382            || !isset($_SESSION['__HTTP_Session2_Idle'])) {
383            return 0;
384        }
385        return $_SESSION['__HTTP_Session2_Idle_TS']
386            + $_SESSION['__HTTP_Session2_Idle'];
387    }
388
389    /**
390     * Check if session is expired
391     *
392     * @return boolean
393     */
394    public static function isExpired()
395    {
396        if (
397            isset($_SESSION['__HTTP_Session2_Expire'])
398            && $_SESSION['__HTTP_Session2_Expire'] > 0
399            && isset($_SESSION['__HTTP_Session2_Expire_TS'])
400            &&
401            (
402                $_SESSION['__HTTP_Session2_Expire_TS']
403                + $_SESSION['__HTTP_Session2_Expire']
404            ) <= time()) {
405            return true;
406        }
407        return false;
408    }
409
410    /**
411     * Check if session is idle
412     *
413     * @return boolean Obvious
414     */
415    public static function isIdle()
416    {
417        if (
418            isset($_SESSION['__HTTP_Session2_Idle'])
419            && $_SESSION['__HTTP_Session2_Idle'] > 0
420            && isset($_SESSION['__HTTP_Session2_Idle_TS'])
421            && (
422                $_SESSION['__HTTP_Session2_Idle_TS']
423                + $_SESSION['__HTTP_Session2_Idle']
424            ) <= time()) {
425            return true;
426        }
427        return false;
428    }
429
430    /**
431     * Updates the idletime
432     *
433     * @return void
434     */
435    public static function updateIdle()
436    {
437        if (isset($_SESSION['__HTTP_Session2_Idle_TS'])) {
438            $_SESSION['__HTTP_Session2_Idle_TS'] = time();
439        }
440    }
441
442    /**
443     * If optional parameter is specified it indicates whether the module will
444     * use cookies to store the session id on the client side in a cookie.
445     *
446     * By default this cookie will be deleted when the browser is closed!
447     *
448     * It will throw an Exception if it's not able to set the session.use_cookie
449     * property.
450     *
451     * It returns the previous value of this property.
452     *
453     * @param boolean $useCookies If specified it will replace the previous value of
454     *                            this property. By default 'null', which doesn't
455     *                            change any setting on your system. If you supply a
456     *                            parameter, please supply 'boolean'.
457     *
458     * @return boolean The previous value of the property
459     *
460     * @throws HTTP_Session2_Exception If ini_set() fails!
461     * @see    session_set_cookie_params()
462     * @link   http://php.net/manual/en/function.session-set-cookie-params.php
463     */
464    public static function useCookies($useCookies = null)
465    {
466        $return = false;
467        if (ini_get('session.use_cookies') == '1') {
468            $return = true;
469        }
470        if ($useCookies !== null) {
471            if ($useCookies === true) {
472                $status = ini_set('session.use_cookies', 1);
473            } else {
474                $status = ini_set('session.use_cookies', 0);
475            }
476            if ($status === false) {
477                $msg  = "Could not set 'session.use_cookies'. Please check your ";
478                $msg .= 'permissions to override php.ini-settings. E.g. a possible ';
479                $msg .= 'php_admin_value setting or blocked ini_set() calls ';
480                throw new HTTP_Session2_Exception($msg, self::ERR_SYSTEM_PERM);
481            }
482        }
483        return $return;
484    }
485
486    /**
487     * Gets a value indicating whether the session
488     * was created with the current request
489     *
490     * You MUST call this method only after you have started
491     * the session with the HTTP_Session2::start() method.
492     *
493     * @return boolean true when the session was created with the current request
494     *                 false otherwise
495     *
496     * @see  self::start()
497     * @uses self::STARTED
498     */
499    public static function isNew()
500    {
501        // The best way to check if a session is new is to check
502        // for existence of a session data storage
503        // with the current session id, but this is impossible
504        // with the default PHP module wich is 'files'.
505        // So we need to emulate it.
506        return !isset($_SESSION['__HTTP_Session2_Info']) ||
507            $_SESSION['__HTTP_Session2_Info'] == self::STARTED;
508    }
509
510    /**
511     * Register variable with the current session
512     *
513     * @param string $name Name of a global variable
514     *
515     * @return void
516     * @see session_register()
517     */
518    public static function register($name)
519    {
520        session_register($name);
521    }
522
523    /**
524     * Unregister a variable from the current session
525     *
526     * @param string $name Name of a global variable
527     *
528     * @return void
529     * @see    session_unregister()
530     */
531    public static function unregister($name)
532    {
533        session_unregister($name);
534    }
535
536    /**
537     * Returns session variable
538     *
539     * @param string $name    Name of a variable
540     * @param mixed  $default Default value of a variable if not set
541     *
542     * @return mixed  Value of a variable
543     */
544    public static function &get($name, $default = null)
545    {
546        if (!isset($_SESSION[$name]) && isset($default)) {
547            $_SESSION[$name] = $default;
548        }
549        return $_SESSION[$name];
550    }
551
552    /**
553     * Sets session variable
554     *
555     * @param string $name  Name of a variable
556     * @param mixed  $value Value of a variable
557     *
558     * @return mixed Old value of a variable
559     */
560    public static function set($name, $value)
561    {
562        $return = (isset($_SESSION[$name])) ? $_SESSION[$name] : null;
563        if (null === $value) {
564            unset($_SESSION[$name]);
565        } else {
566            $_SESSION[$name] = $value;
567        }
568        return $return;
569    }
570
571    /**
572     * Returns local variable of a script
573     *
574     * Two scripts can have local variables with the same names
575     *
576     * @param string $name    Name of a variable
577     * @param mixed  $default Default value of a variable if not set
578     *
579     * @return mixed  Value of a local variable
580     */
581    static function &getLocal($name, $default = null)
582    {
583        $local = md5(self::localName());
584        if (!is_array($_SESSION[$local])) {
585            $_SESSION[$local] = array();
586        }
587        if (!isset($_SESSION[$local][$name]) && isset($default)) {
588            $_SESSION[$local][$name] = $default;
589        }
590        return $_SESSION[$local][$name];
591    }
592
593    /**
594     * Sets local variable of a script.
595     * Two scripts can have local variables with the same names.
596     *
597     * @param string $name  Name of a local variable
598     * @param mixed  $value Value of a local variable
599     *
600     * @return mixed Old value of a local variable
601     */
602    static function setLocal($name, $value)
603    {
604        $local = md5(self::localName());
605        if (!is_array($_SESSION[$local])) {
606            $_SESSION[$local] = array();
607        }
608        $return = $_SESSION[$local][$name];
609        if (null === $value) {
610            unset($_SESSION[$local][$name]);
611        } else {
612            $_SESSION[$local][$name] = $value;
613        }
614        return $return;
615    }
616
617    /**
618     * set the usage of transparent SID
619     *
620     * @param boolean $useTransSID Flag to use transparent SID
621     *
622     * @return boolean
623     */
624    static function useTransSID($useTransSID = false)
625    {
626        $return = ini_get('session.use_trans_sid') ? true : false;
627        if ($useTransSID === false) {
628            ini_set('session.use_trans_sid', $useTransSID ? 1 : 0);
629        }
630        return $return;
631    }
632
633    /**
634     * Sets new local name
635     *
636     * @param string $name New local name
637     *
638     * @return string Previous local name
639     */
640    static function localName($name = null)
641    {
642        $return = '';
643        if (isset($GLOBALS['__HTTP_Session2_Localname'])) {
644            $return .= $GLOBALS['__HTTP_Session2_Localname'];
645        }
646        if (!empty($name)) {
647            $GLOBALS['__HTTP_Session2_Localname'] = $name;
648        }
649        return $return;
650    }
651
652    /**
653     * init
654     *
655     * @return void
656     */
657    static function init()
658    {
659        // Disable auto-start of a sesion
660        ini_set('session.auto_start', 0);
661
662        // Set local name equal to the current script name
663        self::localName($_SERVER['SCRIPT_NAME']);
664    }
665
666    /**
667     * Regenrates session id
668     *
669     * If session_regenerate_id() is not available emulates its functionality
670     *
671     * @param boolean $deleteOldSessionData Whether to delete data of old session
672     *
673     * @return boolean
674     */
675    public static function regenerateId($deleteOldSessionData = false)
676    {
677        if (function_exists('session_regenerate_id')) {
678            return session_regenerate_id($deleteOldSessionData);
679
680            // emulate session_regenerate_id()
681        } else {
682
683            do {
684                $newId = uniqid(dechex(rand()));
685            } while ($newId === session_id());
686
687            if ($deleteOldSessionData) {
688                session_unset();
689            }
690
691            session_id($newId);
692
693            return true;
694        }
695    }
696
697    /**
698     * This function copies session data of specified id to specified table
699     *
700     * @param string $target Target to replicate to
701     * @param string $id     Id of record to replicate
702     *
703     * @return boolean
704     */
705    public static function replicate($target, $id = null)
706    {
707        return self::$container->replicate($target, $id);
708    }
709
710    /**
711     * If optional parameter is specified it determines the number of seconds
712     * after which session data will be seen as 'garbage' and cleaned up
713     *
714     * It returns the previous value of this property
715     *
716     * @param int $gcMaxLifetime If specified it will replace the previous value of
717     *                           this property, and must be integer.
718     *
719     * @return boolean The previous value of the property
720     */
721    public static function setGcMaxLifetime($gcMaxLifetime = null)
722    {
723        $return = ini_get('session.gc_maxlifetime');
724        if (isset($gcMaxLifetime) && is_int($gcMaxLifetime) && $gcMaxLifetime >= 1) {
725            ini_set('session.gc_maxlifetime', $gcMaxLifetime);
726        }
727        return $return;
728    }
729
730    /**
731     * If optional parameter is specified it determines the
732     * probability that the gc (garbage collection) routine is started
733     * and session data is cleaned up
734     *
735     * It returns the previous value of this property
736     *
737     * @param int $gcProbability If specified it will replace the previous value of
738     *                           this property.
739     *
740     * @return boolean The previous value of the property
741     */
742    public static function setGcProbability($gcProbability = null)
743    {
744        $return = ini_get('session.gc_probability');
745        if (isset($gcProbability)  &&
746            is_int($gcProbability) &&
747            $gcProbability >= 1    &&
748            $gcProbability <= 100) {
749            ini_set('session.gc_probability', $gcProbability);
750        }
751        return $return;
752    }
753}
754
755/**
756 * init {@link HTTP_Session2}
757 *
758 * @see HTTP_Session2::init()
759 */
760HTTP_Session2::init();
761