eZSession enhancements in eZ Publish 4.4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

=====
Intro
=====

eZSession has gotten a few changes in this version:

 1. Support session handlers
 2. Changed to use PHP native session handler by default for performance
 3. Support session less anonymous users using lazy session starting
   3.1. This affects session variables like LastAccessesURI

This has caused several changes in the kernel, things to look out for:

 - Cluster: You need to setup sessions to be shared across application servers,
   easiest way is to use provided DB handler: ezpSessionHandlerDB

 - Debian based Linux distros(incl Ubuntu): These Linux distributions tend to use
   their own session gc approach with a cronjob. maxliftime (/usr/lib/php5/maxlifetime)
   only reads session.gc_maxlifetime using /etc/php5/*/php.ini. Meaning you'll need to place
   this setting in for instance /etc/php5/apache2/php.ini or /etc/php5/conf.d/php.ini to 
   something way higher then the default 1440 (24 minutes). For instance 259200 (72 hours)
   which is the default value of site.ini\[Session]\SessionTimeout as used by DB session handler.

 - Directly usage of $_SESSION: This has never been recommended, yet possible. As of now, it is not longer possible.
   Any direct calls to this variable will cause errors if session has not started yet, and hence
   you should either use API that always been there (eZHTTPTool), or the new one provided by
   eZSession (see "Anonymous Sessions" for examples).

 - If you want to avoid losing sessions when you upgrade you should setup DB handler in site.ini\[Session]\
   Handler=ezpSessionHandlerDB in advance.

 - Additional BC-related breaks are listed in doc/bc/4.4/


===================
1. Session handlers
===================

Session handlers are classes that extends ezpSessionHandler (lib/ezsession/classes/ezpsessionhandler.php).
If you intend to create your own session handler, make sure you look at that and also take a look at how
this is implemented in practice in ezpSessionHandlerPHP and ezpSessionHandlerDB handler in the same folder.

When changing session handler, remember that all users will loose their session and will have to log in again unless
you implement a fallback to old sessions, in a custom session handler. An example would be to create a memcache
eZ Publish session handler that falls back to the same db backend as ezpSessionHandlerDB does for persistence storage.
The setting involved is site.ini\[Session]\Handler, it uses the autoload system, so make sure the class you specify
is correct and that you have regenerated the autoload data, if you have added your own session handler.

===============================
2. Native (PHP) Session handler
===============================

This is the handler now used by default, it lets PHP handle the sessions, thus you are free to use any PHP session
handlers (session.save_handler) available. The default is 'files', meaning sessions are stored in files.
There is also Memcached based session handlers in the Memcache & Memcached php extensions. You are free to try them
out, but we have not been able to test these yet, so make sure you set it up with redundancy in mind so sessions aren't
cleared when memecache runs out of memory. And if possible when it's updated or restarted.


=====================
3. Anonymous Sessions
=====================

Anonymous sessions are implemented to be, as backwards-compatible as possible, using lazy loading of sessions.
Meaning if current user does not have a session cookie, or session data in post variables (as used by
ezmultiupload to workaround flash cookie issues) session will not start UNLESS some code tries to access
session variables.

For extension / kernel developers:
This means that to take advantage of this and not cause sessions to start by your own code, you'll need to
do some changes. There are three alternatives:

 - Using eZSession::hasStarted() and support older versions of eZ Publish 4:

       if ( !class_exists('eZSession',false) || !method_exists('eZSession','hasStarted') || eZSession::hasStarted() )
       {
           // use session as normal using eZHTTPTool
       }
       else
       {
           // optionally get default values for anonymous users
       }

 - Using eZSession::userHasSessionCookie() on eZ Publish 4.1 and up

       Note: this function does not reflect if session was started by other calls to sessions, but it will give
       you indication that user did not have a session (cookie or post) when request started and thus the session
       value that is only specific for your code is not there yet.

       Note 2: This is especially useful to validate if client accepts cookies as used by user/register to be able
       to ignore search crawlers and scripted requests like the once coming from the static cache feature.

       if ( eZSession::userHasSessionCookie() )
       {
           // use session as normal using eZHTTPTool
       }
       else
       {
           // optionally get default values for anonymous users
       }

 - Using eZSession API directly on 4.4+ (from the inline phpdoc):

        /**
         * Get session value (wrapper)
         *
         * @param string|null $key Return the whole session array if null otherwise the value of $key
         * @param null|mixed $defaultValue Return this if not null and session has not started
         * @return mixed|null $defaultValue if key does not exist, otherwise session value depending on $key
         */
        static public function &get( $key = null, $defaultValue = null )

        /**
         * Isset session value (wrapper)
         *
         * @param string $key
         * @param bool $forceStart Force session start if true
         * @return bool|null Null if session has not started and $forceStart is false
         */
        static public function issetkey( $key, $forceStart = true )

        /**
         * unset session value (wrapper)
         *
         * @param string $key
         * @param bool $forceStart Force session start if true
         * @return bool|null True if value was removed, false if it did not exist and
         *                   null if session is not started and $forceStart is false
         */
        static public function unsetkey( $key, $forceStart = true )


3.1 LastAccessesURI
-------------------

LastAccessesURI is not set anymore for session-less users, so to be able to handle that you should change your
code to to check if user has a session cookie and provide a default redirect if it does not. The session value
is only useful if it was set by previous page anyway.
There are two approaches to fix this, the proper way and the easy way.

 - The proper approach would be to always provide a post variable in your forms that initiates an action, redirecting to a
   edit view or some other kind of action using post forms. The post variable should be named 'RedirectURI', at least that
   is what the rest of the kernel uses now.

 - The simple way is to just provide a default fallback url in case there is nothing in (post from proper way or) session.
   In 4.4 and up you can do it like this using fallbacks:

       $redirectURI = $http->sessionVariable('LastAccessesURI', '/');

   or even better preparing your code to be able to accept uri by Post variable as well taking advantage of fallbacks twice:

       $redirectURI = $http->postVariable('RedirectURI', $http->sessionVariable('LastAccessesURI', '/'));

   So what is going on here? basically it will look for post variable 'RedirectURI', if that does not exist it will use LastAccessesURI
   and if session has not started it will use '/', aka frontpage.

   Unfortunately fallbacks was added in 4.4 as well, so for 4.1+ you'll have to do it like this:
   (the hasSessionVariable() is not needed but makes the code future proof in case we decide to remove it)

       $redirectURI = eZSession::userHasSessionCookie() && $http->hasSessionVariable('LastAccessesURI') ?
                      $http->sessionVariable('LastAccessesURI') :
                      '/';

   Or preferably if you want to add support for post params as well like this:

       if ( $http->hasPostVariable('RedirectURI') )
       {
           $redirectURI = $http->postVariable('RedirectURI');
       }
       else if ( eZSession::userHasSessionCookie() && $http->hasSessionVariable('LastAccessesURI') )
       {
           $redirectURI = $http->sessionVariable('LastAccessesURI');
       }
       else
       {
           $redirectURI = '/';
       }
