Version: current

      1 <?php
      2 /**
      3  * Handles all manipulation of the session.
      4  *
      5  * The static methods are used to manipulate the currently active controller's session.
      6  * The instance methods are used to manipulate a particular session.  There can be more than one of these created.
      7  *
      8  * In order to support things like testing, the session is associated with a particular Controller.  In normal usage, this is loaded from
      9  * and saved to the regular PHP session, but for things like static-page-generation and unit-testing, you can create multiple Controllers,
     10  * each with their own session.
     11  *
     12  * The instance object is basically just a way of manipulating a set of nested maps, and isn't specific to session data.
     13  *
     14  * <b>Saving Data</b>
     15  *
     16  * You can write a value to a users session from your PHP code using the static function {@link Session::set()}. You can add this line in any function or file you wish to save the value.
     17  *
     18  * <code>
     19  *  Session::set('MyValue', 6);
     20  * </code>
     21  *
     22  * Saves the value of "6" to the MyValue session data. You can also save arrays or serialized objects in session (but note there may be size restrictions as to how much you can save)
     23  *
     24  * <code>
     25  *  // save a variable
     26  *  $var = 1;
     27  *  Session::set('MyVar', $var);
     28  *
     29  *  // saves an array
     30  *  Session::set('MyArrayOfValues', array('1','2','3'));
     31  *
     32  *  // saves an object (you'll have to unserialize it back)
     33  *  $object = new Object();
     34  *
     35  *  Session::set('MyObject', serialize($object));
     36  * </code>
     37  *
     38  * <b>Accessing Data</b>
     39  *
     40  * Once you have saved a value to the Session you can access it by using the {@link Session::get()} function.
     41  * Like the {@link Session::set()} function you can use this anywhere in your PHP files.
     42  *
     43  * The values in the comments are the values stored from the previous example.
     44  *
     45  * <code>
     46  * function bar() {
     47  *  $value = Session::get('MyValue'); // $value = 6
     48  *  $var   = Session::get('MyVar'); // $var = 1
     49  *  $array = Session::get('MyArrayOfValues'); // $array = array(1,2,3)
     50  *  $object = Session::get('MyObject', unserialize($object)); // $object = Object()
     51  * }
     52  * </code>
     53  *
     54  * You can also get all the values in the session at once. This is useful for debugging.
     55  *
     56  * <code>
     57  * Session::getAll(); // returns an array of all the session values.
     58  * </code>
     59  *
     60  * <b>Clearing Data</b>
     61  *
     62  * Once you have accessed a value from the Session it doesn't automatically wipe the value from the Session, you have to specifically remove it. To clear a value you can either delete 1 session value by the name that you saved it
     63  *
     64  * <code>
     65  * Session::clear('MyValue'); // myvalue is no longer 6.
     66  * </code>
     67  *
     68  * Or you can clear every single value in the session at once. Note SilverStripe stores some of its own session data including form and page comment information. None of this is vital but clear_all will clear everything.
     69  *
     70  * <code>
     71  *  Session::clearAll();
     72  * </code>
     73  *
     74  * @see Cookie
     75  * @todo This class is currently really basic and could do with a more well-thought-out implementation.
     76  *
     77  *
     78  * @package sapphire
     79  * @subpackage control
     80  */
     81
     82 class Session {
     83
     84     /**
     85      * @var $timeout Set session timeout
     86      */
     87     protected static $timeout = 0;
     88
     89     protected static $session_ips = array();
     90
     91     protected static $cookie_domain;
     92
     93     protected static $cookie_path;
     94
     95     protected static $cookie_secure = false;
     96
     97     /**
     98      * Session data
     99      */
    100     protected $data = array();
    101
    102     protected $changedData = array();
    103
    104     /**
    105      * Cookie domain, for example 'www.php.net'.
    106      *
    107      * To make cookies visible on all subdomains then the domain
    108      * must be prefixed with a dot like '.php.net'.
    109      *
    110      * @param string $domain The domain to set
    111      */
    112     public static function set_cookie_domain($domain) {
    113         self::$cookie_domain = $domain;
    114     }
    115
    116     /**
    117      * Get the cookie domain.
    118      * @return string
    119      */
    120     public static function get_cookie_domain() {
    121         return self::$cookie_domain;
    122     }
    123
    124     /**
    125      * Path to set on the domain where the session cookie will work.
    126      * Use a single slash ('/') for all paths on the domain.
    127      *
    128      * @param string $path The path to set
    129      */
    130     public static function set_cookie_path($path) {
    131         self::$cookie_path = $path;
    132     }
    133
    134     /**
    135      * Get the path on the domain where the session cookie will work.
    136      * @return string
    137      */
    138     public static function get_cookie_path() {
    139         if(self::$cookie_path) {
    140             return self::$cookie_path;
    141         } else {
    142             return Director::baseURL();
    143         }
    144     }
    145
    146     /**
    147      * Secure cookie, tells the browser to only send it over SSL.
    148      * @param boolean $secure
    149      */
    150     public static function set_cookie_secure($secure) {
    151         self::$cookie_secure = (bool) $secure;
    152     }
    153
    154     /**
    155      * Get if the cookie is secure
    156      * @return boolean
    157      */
    158     public static function get_cookie_secure() {
    159         return (bool) self::$cookie_secure;
    160     }
    161
    162     /**
    163      * Create a new session object, with the given starting data
    164      *
    165      * @param $data Can be an array of data (such as $_SESSION) or another Session object to clone.
    166      */
    167     function __construct($data) {
    168         if($data instanceof Session) $data = $data->inst_getAll();
    169
    170         $this->data = $data;
    171     }
    172
    173     /**
    174      * Provide an <code>array</code> of rules specifing timeouts for IPv4 address ranges or
    175      * individual IPv4 addresses. The key is an IP address or range and the value is the time
    176      * until the session expires in seconds. For example:
    177      *
    178      * Session::set_timeout_ips(array(
    179      *      '127.0.0.1' => 36000
    180      * ));
    181      *
    182      * Any user connecting from 127.0.0.1 (localhost) will have their session expired after 10 hours.
    183      *
    184      * Session::set_timeout is used to set the timeout value for any users whose address is not in the given IP range.
    185      *
    186      * @param array $session_ips Array of IPv4 rules.
    187      */
    188     public static function set_timeout_ips($session_ips) {
    189         if(!is_array($session_ips)) {
    190             user_error("Session::set_timeout_ips expects an array as its argument", E_USER_NOTICE);
    191             self::$session_ips = array();
    192         } else {
    193             self::$session_ips = $session_ips;
    194         }
    195     }
    196
    197     /**
    198      * @deprecated 2.5 Use Session::add_to_array($name, $val) instead
    199      */
    200     public static function addToArray($name, $val) {
    201         user_error('Session::addToArray() is deprecated. Please use Session::add_to_array() instead.', E_USER_NOTICE);
    202
    203         return Session::add_to_array($name, $val);
    204     }
    205
    206     /**
    207      * Add a value to a specific key in the session array
    208      */
    209     public static function add_to_array($name, $val) {
    210         return self::current_session()->inst_addToArray($name, $val);
    211     }
    212
    213     /**
    214      * Set a key/value pair in the session
    215      *
    216      * @param string $name Key
    217      * @param string $val Value
    218      */
    219     public static function set($name, $val) {
    220         return self::current_session()->inst_set($name, $val);
    221     }
    222
    223     /**
    224      * Return a specific value by session key
    225      *
    226      * @param string $name Key to lookup
    227      */
    228     public static function get($name) {
    229         return self::current_session()->inst_get($name);
    230     }
    231
    232     /**
    233      * Return all the values in session
    234      *
    235      * @return Array
    236      */
    237     public static function get_all() {
    238         return self::current_session()->inst_getAll();
    239     }
    240
    241     /**
    242      * @deprecated 2.5 Use Session::get_all()
    243      */
    244     public static function getAll() {
    245         user_error('Session::getAll() is deprecated. Please use Session::get_all() instead.', E_USER_NOTICE);
    246
    247         return Session::get_all();
    248     }
    249
    250     /**
    251      * Clear a given session key, value pair.
    252      *
    253      * @param string $name Key to lookup
    254      */
    255     public static function clear($name) {
    256         return self::current_session()->inst_clear($name);
    257     }
    258
    259     /**
    260      * Clear all the values
    261      */
    262     public static function clear_all() {
    263         $ret = self::current_session()->inst_clearAll();
    264         self::$default_session = null;
    265
    266         return $ret;
    267     }
    268
    269     /**
    270      * @deprecated 2.5 Use Session::clear_all()
    271      */
    272     public static function clearAll() {
    273         user_error('Session::clearAll() is deprecated. Please use Session::clear_all() instead.', E_USER_NOTICE);
    274
    275         return Session::clear_all();
    276     }
    277
    278     /**
    279      * Save all the values in our session to $_SESSION
    280      */
    281     public static function save() {
    282         return self::current_session()->inst_save();
    283     }
    284
    285     protected static $default_session = null;
    286
    287     protected static function current_session() {
    288         if (Controller::has_curr() && $session =  Controller::curr()->getSession()) {
    289             return $session;
    290         }
    291
    292         if(!self::$default_session) self::$default_session = new Session(isset($_SESSION) ? $_SESSION : array());
    293         return self::$default_session;
    294     }
    295
    296     public function inst_set($name, $val) {
    297         // Quicker execution path for "."-free names
    298         if(strpos($name,'.') === false) {
    299             $this->data[$name] = $val;
    300             $this->changedData[$name] = $val;
    301
    302         } else {
    303             $names = explode('.', $name);
    304
    305             // We still want to do this even if we have strict path checking for legacy code
    306             $var = &$this->data;
    307             $diffVar = &$this->changedData;
    308
    309             foreach($names as $n) {
    310                 $var = &$var[$n];
    311                 $diffVar = &$diffVar[$n];
    312             }
    313
    314             $var = $val;
    315             $diffVar = $val;
    316         }
    317     }
    318
    319     public function inst_addToArray($name, $val) {
    320         $names = explode('.', $name);
    321
    322         // We still want to do this even if we have strict path checking for legacy code
    323         $var = &$this->data;
    324         $diffVar = &$this->changedData;
    325
    326         foreach($names as $n) {
    327             $var = &$var[$n];
    328             $diffVar = &$diffVar[$n];
    329         }
    330
    331         $var[] = $val;
    332         $diffVar[sizeof($var)-1] = $val;
    333     }
    334
    335     public function inst_get($name) {
    336         // Quicker execution path for "."-free names
    337         if(strpos($name,'.') === false) {
    338             if(isset($this->data[$name])) return $this->data[$name];
    339
    340         } else {
    341             $names = explode('.', $name);
    342
    343             if(!isset($this->data)) {
    344                 return null;
    345             }
    346
    347             $var = $this->data;
    348
    349             foreach($names as $n) {
    350                 if(!isset($var[$n])) {
    351                     return null;
    352                 }
    353                 $var = $var[$n];
    354             }
    355
    356             return $var;
    357         }
    358     }
    359
    360     public function inst_clear($name) {
    361         $names = explode('.', $name);
    362
    363         // We still want to do this even if we have strict path checking for legacy code
    364         $var = &$this->data;
    365         $diffVar = &$this->changedData;
    366
    367         foreach($names as $n) {
    368             $var = &$var[$n];
    369             $diffVar = &$diffVar[$n];
    370         }
    371
    372         $var = null;
    373         $diffVar = null;
    374     }
    375
    376     public function inst_clearAll() {
    377         if($this->data && is_array($this->data)) {
    378             foreach(array_keys($this->data) as $key) {
    379                 $this->inst_clear($key);
    380             }
    381         }
    382     }
    383
    384     public function inst_getAll() {
    385         return $this->data;
    386     }
    387
    388     /**
    389      * Save data to session
    390      * Only save the changes, so that anyone manipulating $_SESSION directly doesn't get burned.
    391      */
    392     public function inst_save() {
    393         $this->recursivelyApply($this->changedData, $_SESSION);
    394     }
    395
    396     /**
    397      * Recursively apply the changes represented in $data to $dest.
    398      * Used to update $_SESSION
    399      */
    400     protected function recursivelyApply($data, &$dest) {
    401         foreach($data as $k => $v) {
    402             if(is_array($v)) {
    403                 if(!isset($dest[$k])) $dest[$k] = array();
    404                 $this->recursivelyApply($v, $dest[$k]);
    405             } else {
    406                 $dest[$k] = $v;
    407             }
    408         }
    409     }
    410
    411     /**
    412     * Sets the appropriate form message in session, with type. This will be shown once,
    413     * for the form specified.
    414     *
    415     * @param formname the form name you wish to use ( usually $form->FormName() )
    416     * @param messsage the message you wish to add to it
    417     * @param type the type of message
    418     */
    419     public static function setFormMessage($formname,$message,$type){
    420         Session::set("FormInfo.$formname.message", $message);
    421         Session::set("FormInfo.$formname.type", $type);
    422     }
    423
    424     /**
    425      * Initialize session.
    426      *
    427      * @param string $sid Start the session with a specific ID
    428      */
    429     public static function start($sid = null) {
    430         self::load_config();
    431         $path = self::get_cookie_path();
    432         $domain = self::get_cookie_domain();
    433         $secure = self::get_cookie_secure();
    434
    435         if(!session_id() && !headers_sent()) {
    436             if($domain) {
    437                 session_set_cookie_params(self::$timeout, $path, $domain, $secure /* secure */, true /* httponly */);
    438             } else {
    439                 session_set_cookie_params(self::$timeout, $path, null, $secure /* secure */, true /* httponly */);
    440             }
    441
    442             // @ is to supress win32 warnings/notices when session wasn't cleaned up properly
    443             // There's nothing we can do about this, because it's an operating system function!
    444             if($sid) session_id($sid);
    445             @session_start();
    446         }
    447     }
    448
    449     /**
    450      * Destroy the active session.
    451      *
    452      * @param bool $removeCookie If set to TRUE, removes the user's cookie, FALSE does not remove
    453      */
    454     public static function destroy($removeCookie = true) {
    455         if(session_id()) {
    456             if($removeCookie) {
    457                 setcookie(session_name(), '');
    458                 unset($_COOKIE[session_name()]);
    459             }
    460             session_destroy();
    461         }
    462     }
    463
    464     /**
    465      * Use the Session::$session_ips array to set timeouts based on IP address or IP address
    466      * range.
    467      *
    468      * Note: The use of _sessions.php is deprecated.
    469      */
    470     public static function load_config() {
    471         foreach(self::$session_ips as $sessionIP => $timeout) {
    472             if(preg_match('/^([0-9.]+)\s?-\s?([0-9.]+)$/', $sessionIP, $ips)) {
    473                 if(isset($_SERVER['REMOTE_ADDR'])) {
    474                     $startIP = ip2long($ips[1]);
    475                     $endIP = ip2long($ips[2]);
    476                     $clientIP = ip2long($_SERVER['REMOTE_ADDR']);
    477                     $minIP = min($startIP, $endIP);
    478                     $maxIP = max($startIP, $endIP);
    479
    480                     if($minIP <= $clientIP && $clientIP <= $maxIP) {
    481                         return self::set_timeout($timeout);
    482                     }
    483                 }
    484             }
    485             // TODO - Net masks or something
    486         }
    487     }
    488
    489     /**
    490      * Set the timeout of a Session value
    491      *
    492      * @param int $timeout Time until a session expires in seconds. Defaults to expire when browser is closed.
    493      */
    494     public static function set_timeout($timeout) {
    495         self::$timeout = intval($timeout);
    496     }
    497
    498     public static function get_timeout() {
    499         return self::$timeout;
    500     }
    501 }
    502

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -