eZ Publish  [trunk]
ezsession.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing session interface
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /**
00012  * eZ Publish Session interface class
00013  *
00014  * Session wrapper for session management, with support for handlers.
00015  * Handler is defined by site.ini\[Session]\Handler setting.
00016  *
00017  * The session system has a hook system which allows external code to perform
00018  * extra tasks before and after a certain action is executed. For instance to
00019  * cleanup a table when sessions are removed.
00020  * This can be used by adding a callback with the eZSession::addCallback function,
00021  * first param is type and second is callback (called with call_user_func_array)
00022  *
00023  * \code
00024  * function cleanupStuff( $db, $key, $escKey )
00025  * {
00026  *     // Do cleanup here
00027  * }
00028  *
00029  * eZSession::addCallback( 'destroy_pre', 'cleanupstuff');
00030  * // Or if it was a class function:
00031  * // eZSession::addCallback( 'destroy_pre', array('myClass', 'myCleanupStuff') );
00032  * \endcode
00033  *
00034  * When a specific session is destroyed in the database it will call the
00035  * \c destroy_pre and \c destroy_post hooks. The signature of the function is
00036  * function destroy( $db, $key, $escapedKey )
00037  *
00038  * When a specific session is regenerated (login/logout) and kept it will call
00039  * \c regenerate_pre and \c regenerate_post hooks. The signature of the function is
00040  * function regenerate( $db, $escapedNewKey, $escapedOldKey, $escUserID )
00041  *
00042  * When multiple sessions are expired (garbage collector) in the database it
00043  * will call the \c gc_pre and \c gc_post hooks. The signature of the function is
00044  * function gcollect( $db, $expiredTime )
00045  *
00046  * When all sessions are removed from the database it will call the
00047  * \c cleanup_pre and \c cleanup_post hooks. The signature of the function is
00048  * function cleanup( $db )
00049  *
00050  * \param $db The database object used by the session manager.
00051  * \param $key The session key which are being worked on, see also \a $escapedKey
00052  * \param $escapedKey The same key as \a $key but is escaped correctly for the database.
00053  *                    Use this to save a call to eZDBInterface::escapeString()
00054  * \param $expirationTime An integer specifying the timestamp of when the session
00055  *                        will expire.
00056  * \param $expiredTime Similar to \a $expirationTime but is the time used to figure
00057  *                     if a session is expired in the database. ie. all sessions with
00058  *                     lower expiration times will be removed.
00059  *
00060  * @package lib
00061  * @subpackage ezsession
00062  */
00063 class eZSession
00064 {
00065     /**
00066      * User id, see {@link eZSession::userID()}.
00067      *
00068      * @var int
00069      */
00070     static protected $userID = 0;
00071 
00072     /**
00073      * Flag session has started, see {@link eZSession::start()}.
00074      *
00075      * @var bool
00076      */
00077     static protected $hasStarted = false;
00078 
00079     /**
00080      * Flag request contains session cookie, set in {@link eZSession::registerFunctions()}.
00081      *
00082      * @var bool|null
00083      */
00084     static protected $hasSessionCookie = null;
00085 
00086     /**
00087      * List of callback actions, see {@link eZSession::addCallback()}.
00088      *
00089      * @var array
00090      */
00091     static protected $callbackFunctions = array();
00092 
00093     /**
00094      * Current session handler or false, see {@link eZSession::getHandlerInstance()}.
00095      *
00096      * @var ezpSessionHandler|null
00097      */
00098     static protected $handlerInstance = null;
00099 
00100     /**
00101      * Constructor (not used, this is an all static class)
00102      */
00103     protected function __construct()
00104     {
00105     }
00106 
00107     /**
00108      * Get session value (wrapper)
00109      *
00110      * @since 4.4
00111      * @param string|null $key Return the whole session array if null otherwise the value of $key
00112      * @param null|mixed $defaultValue Return this if not null and session has not started
00113      * @return mixed|null $defaultValue if key does not exist, otherwise session value depending on $key
00114      */
00115     static public function &get( $key = null, $defaultValue = null )
00116     {
00117         if ( self::$hasStarted === false )
00118         {
00119             if ( $defaultValue !== null )
00120                 return $defaultValue;
00121             self::start();
00122         }
00123 
00124         if ( $key === null )
00125             return $_SESSION;
00126         else if ( isset( $_SESSION[ $key ] ) )
00127             return $_SESSION[ $key ];
00128         return $defaultValue;
00129     }
00130 
00131     /**
00132      * Set session value (wrapper)
00133      *
00134      * @since 4.4
00135      * @param string $key
00136      * @return bool
00137      */
00138     static public function set( $key, $value )
00139     {
00140         if ( self::$hasStarted === false )
00141         {
00142             self::start();
00143         }
00144 
00145         $_SESSION[ $key ] = $value;
00146         return true;
00147     }
00148 
00149     /**
00150      * Isset session value (wrapper)
00151      *
00152      * @since 4.4
00153      * @param string $key
00154      * @param bool $forceStart Force session start if true
00155      * @return bool|null Null if session has not started and $forceStart is false
00156      */
00157     static public function issetkey( $key, $forceStart = true )
00158     {
00159         if ( self::$hasStarted === false )
00160         {
00161             if ( !$forceStart )
00162                 return null;
00163             self::start();
00164         }
00165 
00166         return isset( $_SESSION[ $key ] );
00167     }
00168 
00169     /**
00170      * unset session value (wrapper)
00171      *
00172      * @since 4.4
00173      * @param string $key
00174      * @param bool $forceStart Force session start if true
00175      * @return bool|null True if value was removed, false if it did not exist and
00176      *                   null if session is not started and $forceStart is false
00177      */
00178     static public function unsetkey( $key, $forceStart = true )
00179     {
00180         if ( self::$hasStarted === false )
00181         {
00182             if ( !$forceStart )
00183                 return null;
00184             self::start();
00185         }
00186 
00187         if ( !isset( $_SESSION[ $key ] ) )
00188             return false;
00189 
00190         unset( $_SESSION[ $key ] );
00191         return true;
00192     }
00193 
00194     /**
00195      * Deletes all expired session data in the database, this function is not supported
00196      * by session handlers that don't have a session backend on their own.
00197      *
00198      * @since 4.1
00199      * @return bool
00200      */
00201     static public function garbageCollector()
00202     {
00203         return self::getHandlerInstance()->gc( (int)$_SERVER['REQUEST_TIME'] );
00204     }
00205 
00206     /**
00207      * Truncates all session data in the database, this function is not supported
00208      * by session handlers that don't have a session backend on their own.
00209      *
00210      * @since 4.1
00211      * @return bool
00212      */
00213     static public function cleanup()
00214     {
00215         return self::getHandlerInstance()->cleanup();
00216     }
00217 
00218     /**
00219      * Counts the number of active session and returns it, this function is not supported
00220      * by session handlers that don't have a session backend on their own.
00221      *
00222      * @since 4.1
00223      * @return string Number of sessions.
00224      */
00225     static public function countActive()
00226     {
00227         return self::getHandlerInstance()->count();
00228     }
00229 
00230     /**
00231      * Register the needed session functions, this is called automatically by
00232      * {@link eZSession::start()}, so only call this if you don't start the session.
00233      *
00234      * @since 4.1
00235      * @return bool Depending on if eZSession is registrated as session handler.
00236     */
00237     static protected function registerFunctions()
00238     {
00239         if ( self::$hasStarted || self::$handlerInstance !== null )
00240             return false;
00241 
00242         $ini = eZINI::instance();
00243         if ( $ini->variable( 'Session', 'SessionNameHandler' ) === 'custom' )
00244         {
00245             $sessionName = $ini->variable( 'Session', 'SessionNamePrefix' );
00246             if ( $ini->variable( 'Session', 'SessionNamePerSiteAccess' ) === 'enabled' )
00247             {
00248                 $access = $GLOBALS['eZCurrentAccess'];
00249                 // Use md5 to make sure name is only consistent of alphanumeric characters
00250                 $sessionName .=  md5( $access['name'] );
00251             }
00252             session_name( $sessionName );
00253         }
00254         else
00255         {
00256             $sessionName = session_name();
00257         }
00258 
00259         // See if user has session, used to avoid reading from db if no session.
00260         // Allow session bye post params for use by flash, but use $_POST directly
00261         // to avoid session double start issues ( #014686 ) caused by eZHTTPTool
00262         if ( isset( $_POST[ $sessionName ] ) )
00263         {
00264             // First use session id from post params (for use in flash upload)
00265             session_id( $_POST[ $sessionName ] );
00266             self::$hasSessionCookie = true;
00267         }
00268         else
00269         {
00270             // else check cookie as used by default
00271             self::$hasSessionCookie = isset( $_COOKIE[ $sessionName ] );
00272         }
00273 
00274         return self::getHandlerInstance()->setSaveHandler();
00275     }
00276 
00277     /**
00278      * Set default cookie parameters based on site.ini settings (fallback to php.ini settings)
00279      * Used by {@link eZSession::registerFunctions()}
00280      * Note: this will only have affect when session is created / re-created
00281      *
00282      * @since 4.4
00283      * @param int|false $lifetime Cookie timeout of session cookie, will read from ini if not set
00284     */
00285     static public function setCookieParams( $lifetime = false )
00286     {
00287         $ini      = eZINI::instance();
00288         $params   = session_get_cookie_params();
00289         if ( $lifetime === false )
00290         {
00291             if ( $ini->hasVariable('Session', 'CookieTimeout')
00292               && $ini->variable('Session', 'CookieTimeout') )
00293                 $lifetime = (int) $ini->variable('Session', 'CookieTimeout');
00294             else
00295                 $lifetime = $params['lifetime'];
00296         }
00297         $path   = $ini->hasVariable('Session', 'CookiePath')     ? $ini->variable('Session', 'CookiePath')     : $params['path'];
00298         $domain = $ini->hasVariable('Session', 'CookieDomain')   ? $ini->variable('Session', 'CookieDomain')   : $params['domain'];
00299         $secure = $ini->hasVariable('Session', 'CookieSecure')   ? $ini->variable('Session', 'CookieSecure')   : $params['secure'];
00300         if ( isset( $params['httponly'] ) ) // only available on PHP 5.2 and up
00301         {
00302             $httponly = $ini->hasVariable('Session', 'CookieHttponly') ? $ini->variable('Session', 'CookieHttponly') : $params['httponly'];
00303             session_set_cookie_params( $lifetime, $path, $domain, $secure, $httponly );
00304         }
00305         else
00306         {
00307             session_set_cookie_params( $lifetime, $path, $domain, $secure );
00308         }
00309     }
00310 
00311     /**
00312      * Starts the session and sets the timeout of the session cookie.
00313      * Multiple calls will be ignored unless you call {@link eZSession::stop()} first.
00314      *
00315      * @since 4.1
00316      * @param int|false $cookieTimeout Use this to set custom cookie timeout.
00317      * @return bool Depending on if session was started.
00318      */
00319     static public function start( $cookieTimeout = false )
00320     {
00321         if ( self::lazyStart( false ) === false )
00322         {
00323              return false;
00324         }
00325         self::setCookieParams( $cookieTimeout );
00326         return self::forceStart();
00327     }
00328 
00329     /**
00330      * Inits eZSession and starts it if user has cookie and $startIfUserHasCookie is true.
00331      *
00332      * @since 4.4
00333      * @param bool $startIfUserHasCookie
00334      * @return bool|null
00335      */
00336     static public function lazyStart( $startIfUserHasCookie = true )
00337     {
00338         if ( self::$hasStarted ||
00339            ( isset( $GLOBALS['eZSiteBasics']['session-required'] ) &&
00340              !$GLOBALS['eZSiteBasics']['session-required'] ) )
00341         {
00342             return false;
00343         }
00344         self::registerFunctions();
00345         if ( $startIfUserHasCookie && self::$hasSessionCookie )
00346         {
00347             self::setCookieParams();
00348             return self::forceStart();
00349         }
00350         return null;
00351     }
00352 
00353     /**
00354      * See {@link eZSession::start()}
00355      *
00356      * @since 4.4
00357      * @return true
00358      */
00359     static protected function forceStart()
00360     {
00361         session_start();
00362         return self::$hasStarted = true;
00363     }
00364 
00365     /**
00366      * Gets/generates the user hash for use in validating the session based on [Session]
00367      * SessionValidation* site.ini settings. The default hash is result of md5('empty').
00368      *
00369      * @since 4.1
00370      * @deprecated as of 4.4, only returns default md5('empty') hash now for BC.
00371      * @return string MD5 hash based on parts of the user ip and agent string.
00372      */
00373     static public function getUserSessionHash()
00374     {
00375         return 'a2e4822a98337283e39f7b60acf85ec9';
00376     }
00377 
00378     /**
00379      * Writes session data and stops the session, if not already stopped.
00380      *
00381      * @since 4.1
00382      * @return bool Depending on if session was stopped.
00383      */
00384     static public function stop()
00385     {
00386         if ( !self::$hasStarted )
00387         {
00388              return false;
00389         }
00390         session_write_close();
00391         self::$hasStarted = false;
00392         self::$handlerInstance = null;
00393         return true;
00394     }
00395 
00396     /**
00397      * Will make sure the user gets a new session ID while keepin the session data.
00398      * This is useful to call on logins, to avoid sessions theft from users.
00399      * NOTE: make sure you set new user id first using {@link eZSession::setUserID()}
00400      *
00401      * @since 4.1
00402      * @param bool $updateBackendData set to false to not update session backend with new session id and user id.
00403      * @return bool Depending on if session was regenerated.
00404      */
00405     static public function regenerate( $updateBackendData = true )
00406     {
00407         if ( !self::$hasStarted )
00408         {
00409              return false;
00410         }
00411         if ( !function_exists( 'session_regenerate_id' ) )
00412         {
00413             return false;
00414         }
00415         if ( headers_sent() )
00416         {
00417             if ( PHP_SAPI !== 'cli' )
00418                 eZDebug::writeWarning( 'Could not regenerate session id, HTTP headers already sent.', __METHOD__ );
00419             return false;
00420         }
00421 
00422         return self::getHandlerInstance()->regenerate( ($updateBackendData && self::$hasSessionCookie) );
00423     }
00424 
00425     /**
00426      * Removes the current session and resets session variables.
00427      * Note: implicit stops session as well!
00428      *
00429      * @since 4.1
00430      * @return bool Depending on if session was removed.
00431      */
00432     static public function remove()
00433     {
00434         if ( !self::$hasStarted )
00435         {
00436              return false;
00437         }
00438         $_SESSION = array();
00439         session_destroy();
00440         self::$hasStarted = false;
00441         self::$handlerInstance = null;
00442         return true;
00443     }
00444 
00445     /**
00446      * Sets the current userID used by ezpSessionHandlerDB::write() on shutdown.
00447      *
00448      * @since 4.1
00449      * @param int $userID to use in {@link ezpSessionHandlerDB::write()}
00450      */
00451     static public function setUserID( $userID )
00452     {
00453         self::$userID = $userID;
00454     }
00455 
00456     /**
00457      * Gets the current user id.
00458      *
00459      * @since 4.1
00460      * @return int User id stored by {@link eZSession::setUserID()}
00461      */
00462     static public function userID()
00463     {
00464         return self::$userID;
00465     }
00466 
00467     /**
00468      * Returns if user had session cookie at start of request or not.
00469      *
00470      * @since 4.1
00471      * @return bool|null Null if session is not started yet.
00472      */
00473     static public function userHasSessionCookie()
00474     {
00475         return self::$hasSessionCookie;
00476     }
00477 
00478     /**
00479      * Returns if user session validated against stored data in db
00480      * or if it was invalidated during the current request.
00481      *
00482      * @since 4.1
00483      * @deprecated as of 4.4, only returns true for bc
00484      * @return bool|null Null if user is not validated yet (for instance a new session).
00485      */
00486     static public function userSessionIsValid()
00487     {
00488         return true;
00489     }
00490 
00491     /**
00492      * Return value to indicate if session has started or not
00493      *
00494      * @since 4.4
00495      * @return bool
00496      */
00497     static public function hasStarted()
00498     {
00499         return self::$hasStarted;
00500     }
00501 
00502     /**
00503      * Get curren session handler
00504      *
00505      * @since 4.4
00506      * @return ezpSessionHandler
00507      */
00508     static public function getHandlerInstance()
00509     {
00510         if ( self::$handlerInstance === null )
00511         {
00512             $ini = eZINI::instance();
00513             if ( $ini->variable( 'Session', 'Handler' ) !== '' )
00514             {
00515                 $optionArray = array( 'iniFile'       => 'site.ini',
00516                                       'iniSection'    => 'Session',
00517                                       'iniVariable'   => 'Handler',
00518                                       'handlerParams' => array( self::$hasSessionCookie ) );
00519 
00520                 $options = new ezpExtensionOptions( $optionArray );
00521                 self::$handlerInstance = eZExtension::getHandlerClass( $options );
00522             }
00523             if ( !self::$handlerInstance instanceof ezpSessionHandler )
00524             {
00525                 self::$handlerInstance = new ezpSessionHandlerPHP( self::$hasSessionCookie );
00526             }
00527         }
00528         return self::$handlerInstance;
00529     }
00530 
00531     /**
00532      * Adds a callback function, to be triggered by {@link eZSession::triggerCallback()}
00533      * when a certain session event occurs.
00534      * Use: eZSession::addCallback('gc_pre', myCustomGarabageFunction );
00535      *
00536      * @since 4.1
00537      * @deprecated since 4.5, use {@link ezpEvent::getInstance()->attach()} with new events
00538      * @param string $type cleanup, gc, destroy, insert and update, pre and post types.
00539      * @param handler $callback a function to call.
00540      */
00541     static public function addCallback( $type, $callback )
00542     {
00543         if ( !isset( self::$callbackFunctions[$type] ) )
00544         {
00545             self::$callbackFunctions[$type] = array();
00546         }
00547         self::$callbackFunctions[$type][] = $callback;
00548     }
00549 
00550     /**
00551      * Triggers callback functions by type, registrated by {@link eZSession::addCallback()}
00552      * Use: eZSession::triggerCallback('gc_pre', array( $db, $time ) );
00553      *
00554      * @since 4.1
00555      * @deprecated since 4.5, use {@link ezpEvent::getInstance()->notify()} with new events
00556      * @param string $type cleanup, gc, destroy, insert and update, pre and post types.
00557      * @param array $params list of parameters to pass to the callback function.
00558      * @return bool
00559      */
00560     static public function triggerCallback( $type, $params )
00561     {
00562         if ( isset( self::$callbackFunctions[$type] ) )
00563         {
00564             foreach( self::$callbackFunctions[$type] as $callback )
00565             {
00566                 call_user_func_array( $callback, $params );
00567             }
00568             return true;
00569         }
00570         return false;
00571     }
00572 }
00573 
00574 ?>