Namespaces

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • Ftp
  • Html
  • HttpContext
  • HttpRequest
  • HttpResponse
  • HttpUploadedFile
  • Session
  • SessionNamespace
  • Uri
  • UriScript
  • User

Interfaces

  • IHttpRequest
  • IHttpResponse
  • IUser

Exceptions

  • FtpException
  • Overview
  • Namespace
  • Class
  • Tree
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Web;
 13: 
 14: use Nette;
 15: 
 16: 
 17: 
 18: /**
 19:  * Provides access to session namespaces as well as session settings and management methods.
 20:  *
 21:  * @author     David Grudl
 22:  */
 23: class Session extends Nette\Object
 24: {
 25:     /** Default file lifetime is 3 hours */
 26:     const DEFAULT_FILE_LIFETIME = 10800;
 27: 
 28:     /** @deprecated */
 29:     public $verificationKeyGenerator;
 30: 
 31:     /** @var bool  is required session ID regeneration? */
 32:     private $regenerationNeeded;
 33: 
 34:     /** @var bool  has been session started? */
 35:     private static $started;
 36: 
 37:     /** @var array default configuration */
 38:     private $options = array(
 39:         // security
 40:         'referer_check' => '',    // must be disabled because PHP implementation is invalid
 41:         'use_cookies' => 1,       // must be enabled to prevent Session Hijacking and Fixation
 42:         'use_only_cookies' => 1,  // must be enabled to prevent Session Fixation
 43:         'use_trans_sid' => 0,     // must be disabled to prevent Session Hijacking and Fixation
 44: 
 45:         // cookies
 46:         'cookie_lifetime' => 0,   // until the browser is closed
 47:         'cookie_path' => '/',     // cookie is available within the entire domain
 48:         'cookie_domain' => '',    // cookie is available on current subdomain only
 49:         'cookie_secure' => FALSE, // cookie is available on HTTP & HTTPS
 50:         'cookie_httponly' => TRUE,// must be enabled to prevent Session Hijacking
 51: 
 52:         // other
 53:         'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,// 3 hours
 54:         'cache_limiter' => NULL,  // (default "nocache", special value "\0")
 55:         'cache_expire' => NULL,   // (default "180")
 56:         'hash_function' => NULL,  // (default "0", means MD5)
 57:         'hash_bits_per_character' => NULL, // (default "4")
 58:     );
 59: 
 60: 
 61: 
 62:     /**
 63:      * Starts and initializes session data.
 64:      * @throws \InvalidStateException
 65:      * @return void
 66:      */
 67:     public function start()
 68:     {
 69:         if (self::$started) {
 70:             return;
 71:         }
 72: 
 73: 
 74:         // start session
 75:         try {
 76:             $this->configure($this->options);
 77:         } catch (\NotSupportedException $e) {
 78:             // ignore?
 79:         }
 80: 
 81:         if (!defined('SID')) {
 82:             Nette\Tools::tryError();
 83:             session_start();
 84:             if (Nette\Tools::catchError($msg)) {
 85:                 @session_write_close(); // this is needed
 86:                 throw new \InvalidStateException($msg);
 87:             }
 88:         }
 89: 
 90:         self::$started = TRUE;
 91:         if ($this->regenerationNeeded) {
 92:             session_regenerate_id(TRUE);
 93:             $this->regenerationNeeded = FALSE;
 94:         }
 95: 
 96:         /* structure:
 97:             __NF: Counter, BrowserKey, Data, Meta
 98:                 DATA: namespace->variable = data
 99:                 META: namespace->variable = Timestamp, Browser, Version
100:         */
101: 
102:         unset($_SESSION['__NT'], $_SESSION['__NS'], $_SESSION['__NM']); // old unused structures
103: 
104:         // initialize structures
105:         $nf = & $_SESSION['__NF'];
106:         if (empty($nf)) { // new session
107:             $nf = array('C' => 0);
108:         } else {
109:             $nf['C']++;
110:         }
111: 
112:         // browser closing detection
113:         $browserKey = $this->getHttpRequest()->getCookie('nette-browser');
114:         if (!$browserKey) {
115:             $browserKey = (string) lcg_value();
116:         }
117:         $browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
118:         $nf['B'] = $browserKey;
119: 
120:         // resend cookie
121:         $this->sendCookie();
122: 
123:         // process meta metadata
124:         if (isset($nf['META'])) {
125:             $now = time();
126:             // expire namespace variables
127:             foreach ($nf['META'] as $namespace => $metadata) {
128:                 if (is_array($metadata)) {
129:                     foreach ($metadata as $variable => $value) {
130:                         if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T']) // whenBrowserIsClosed || Time
131:                             || ($variable !== '' && is_object($nf['DATA'][$namespace][$variable]) && (isset($value['V']) ? $value['V'] : NULL) // Version
132:                                 !== Nette\Reflection\ClassReflection::from($nf['DATA'][$namespace][$variable])->getAnnotation('serializationVersion'))) {
133: 
134:                             if ($variable === '') { // expire whole namespace
135:                                 unset($nf['META'][$namespace], $nf['DATA'][$namespace]);
136:                                 continue 2;
137:                             }
138:                             unset($nf['META'][$namespace][$variable], $nf['DATA'][$namespace][$variable]);
139:                         }
140:                     }
141:                 }
142:             }
143:         }
144: 
145:         register_shutdown_function(array($this, 'clean'));
146:     }
147: 
148: 
149: 
150:     /**
151:      * Has been session started?
152:      * @return bool
153:      */
154:     public function isStarted()
155:     {
156:         return (bool) self::$started;
157:     }
158: 
159: 
160: 
161:     /**
162:      * Ends the current session and store session data.
163:      * @return void
164:      */
165:     public function close()
166:     {
167:         if (self::$started) {
168:             $this->clean();
169:             session_write_close();
170:             self::$started = FALSE;
171:         }
172:     }
173: 
174: 
175: 
176:     /**
177:      * Destroys all data registered to a session.
178:      * @return void
179:      */
180:     public function destroy()
181:     {
182:         if (!self::$started) {
183:             throw new \InvalidStateException('Session is not started.');
184:         }
185: 
186:         session_destroy();
187:         $_SESSION = NULL;
188:         self::$started = FALSE;
189:         if (!$this->getHttpResponse()->isSent()) {
190:             $params = session_get_cookie_params();
191:             $this->getHttpResponse()->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
192:         }
193:     }
194: 
195: 
196: 
197:     /**
198:      * Does session exists for the current request?
199:      * @return bool
200:      */
201:     public function exists()
202:     {
203:         return self::$started || $this->getHttpRequest()->getCookie(session_name()) !== NULL;
204:     }
205: 
206: 
207: 
208:     /**
209:      * Regenerates the session ID.
210:      * @throws \InvalidStateException
211:      * @return void
212:      */
213:     public function regenerateId()
214:     {
215:         if (self::$started) {
216:             if (headers_sent($file, $line)) {
217:                 throw new \InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
218:             }
219:             session_regenerate_id(TRUE);
220: 
221:         } else {
222:             $this->regenerationNeeded = TRUE;
223:         }
224:     }
225: 
226: 
227: 
228:     /**
229:      * Returns the current session ID. Don't make dependencies, can be changed for each request.
230:      * @return string
231:      */
232:     public function getId()
233:     {
234:         return session_id();
235:     }
236: 
237: 
238: 
239:     /**
240:      * Sets the session name to a specified one.
241:      * @param  string
242:      * @return Session  provides a fluent interface
243:      */
244:     public function setName($name)
245:     {
246:         if (!is_string($name) || !preg_match('#[^0-9.][^.]*$#A', $name)) {
247:             throw new \InvalidArgumentException('Session name must be a string and cannot contain dot.');
248:         }
249: 
250:         session_name($name);
251:         return $this->setOptions(array(
252:             'name' => $name,
253:         ));
254:     }
255: 
256: 
257: 
258:     /**
259:      * Gets the session name.
260:      * @return string
261:      */
262:     public function getName()
263:     {
264:         return session_name();
265:     }
266: 
267: 
268: 
269:     /********************* namespaces management ****************d*g**/
270: 
271: 
272: 
273:     /**
274:      * Returns specified session namespace.
275:      * @param  string
276:      * @param  string
277:      * @return SessionNamespace
278:      * @throws \InvalidArgumentException
279:      */
280:     public function getNamespace($namespace, $class = 'Nette\Web\SessionNamespace')
281:     {
282:         if (!is_string($namespace) || $namespace === '') {
283:             throw new \InvalidArgumentException('Session namespace must be a non-empty string.');
284:         }
285: 
286:         if (!self::$started) {
287:             $this->start();
288:         }
289: 
290:         return new $class($_SESSION['__NF']['DATA'][$namespace], $_SESSION['__NF']['META'][$namespace]);
291:     }
292: 
293: 
294: 
295:     /**
296:      * Checks if a session namespace exist and is not empty.
297:      * @param  string
298:      * @return bool
299:      */
300:     public function hasNamespace($namespace)
301:     {
302:         if ($this->exists() && !self::$started) {
303:             $this->start();
304:         }
305: 
306:         return !empty($_SESSION['__NF']['DATA'][$namespace]);
307:     }
308: 
309: 
310: 
311:     /**
312:      * Iteration over all namespaces.
313:      * @return \ArrayIterator
314:      */
315:     public function getIterator()
316:     {
317:         if ($this->exists() && !self::$started) {
318:             $this->start();
319:         }
320: 
321:         if (isset($_SESSION['__NF']['DATA'])) {
322:             return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
323: 
324:         } else {
325:             return new \ArrayIterator;
326:         }
327:     }
328: 
329: 
330: 
331:     /**
332:      * Cleans and minimizes meta structures.
333:      * @return void
334:      */
335:     public function clean()
336:     {
337:         if (!self::$started || empty($_SESSION)) {
338:             return;
339:         }
340: 
341:         $nf = & $_SESSION['__NF'];
342:         if (isset($nf['META']) && is_array($nf['META'])) {
343:             foreach ($nf['META'] as $name => $foo) {
344:                 if (empty($nf['META'][$name])) {
345:                     unset($nf['META'][$name]);
346:                 }
347:             }
348:         }
349: 
350:         if (empty($nf['META'])) {
351:             unset($nf['META']);
352:         }
353: 
354:         if (empty($nf['DATA'])) {
355:             unset($nf['DATA']);
356:         }
357: 
358:         if (empty($_SESSION)) {
359:             //$this->destroy(); only when shutting down
360:         }
361:     }
362: 
363: 
364: 
365:     /********************* configuration ****************d*g**/
366: 
367: 
368: 
369:     /**
370:      * Sets session options.
371:      * @param  array
372:      * @return Session  provides a fluent interface
373:      * @throws \NotSupportedException
374:      * @throws \InvalidStateException
375:      */
376:     public function setOptions(array $options)
377:     {
378:         if (self::$started) {
379:             $this->configure($options);
380:         }
381:         $this->options = $options + $this->options;
382:         if (!empty($options['auto_start'])) {
383:             $this->start();
384:         }
385:         return $this;
386:     }
387: 
388: 
389: 
390:     /**
391:      * Returns all session options.
392:      * @return array
393:      */
394:     public function getOptions()
395:     {
396:         return $this->options;
397:     }
398: 
399: 
400: 
401:     /**
402:      * Configurates session environment.
403:      * @param  array
404:      * @return void
405:      */
406:     private function configure(array $config)
407:     {
408:         $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
409: 
410:         foreach ($config as $key => $value) {
411:             if (!strncmp($key, 'session.', 8)) { // back compatibility
412:                 $key = substr($key, 8);
413:             }
414: 
415:             if ($value === NULL || ini_get("session.$key") == $value) { // intentionally ==
416:                 continue;
417: 
418:             } elseif (strncmp($key, 'cookie_', 7) === 0) {
419:                 if (!isset($cookie)) {
420:                     $cookie = session_get_cookie_params();
421:                 }
422:                 $cookie[substr($key, 7)] = $value;
423: 
424:             } else {
425:                 if (defined('SID')) {
426:                     throw new \InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started by session.auto_start or session_start().");
427:                 }
428:                 if (isset($special[$key])) {
429:                     $key = "session_$key";
430:                     $key($value);
431: 
432:                 } elseif (function_exists('ini_set')) {
433:                     ini_set("session.$key", $value);
434: 
435:                 } else {
436:                     // ignore? this is bad host...
437:                 }
438:             }
439:         }
440: 
441:         if (isset($cookie)) {
442:             session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
443:             if (self::$started) {
444:                 $this->sendCookie();
445:             }
446:         }
447:     }
448: 
449: 
450: 
451:     /**
452:      * Sets the amount of time allowed between requests before the session will be terminated.
453:      * @param  string|int|DateTime  time, value 0 means "until the browser is closed"
454:      * @return Session  provides a fluent interface
455:      */
456:     public function setExpiration($time)
457:     {
458:         if (empty($time)) {
459:             return $this->setOptions(array(
460:                 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
461:                 'cookie_lifetime' => 0,
462:             ));
463: 
464:         } else {
465:             $time = Nette\DateTime::from($time)->format('U') - time();
466:             return $this->setOptions(array(
467:                 'gc_maxlifetime' => $time,
468:                 'cookie_lifetime' => $time,
469:             ));
470:         }
471:     }
472: 
473: 
474: 
475:     /**
476:      * Sets the session cookie parameters.
477:      * @param  string  path
478:      * @param  string  domain
479:      * @param  bool    secure
480:      * @return Session  provides a fluent interface
481:      */
482:     public function setCookieParams($path, $domain = NULL, $secure = NULL)
483:     {
484:         return $this->setOptions(array(
485:             'cookie_path' => $path,
486:             'cookie_domain' => $domain,
487:             'cookie_secure' => $secure
488:         ));
489:     }
490: 
491: 
492: 
493:     /**
494:      * Returns the session cookie parameters.
495:      * @return array  containing items: lifetime, path, domain, secure, httponly
496:      */
497:     public function getCookieParams()
498:     {
499:         return session_get_cookie_params();
500:     }
501: 
502: 
503: 
504:     /**
505:      * Sets path of the directory used to save session data.
506:      * @return Session  provides a fluent interface
507:      */
508:     public function setSavePath($path)
509:     {
510:         return $this->setOptions(array(
511:             'save_path' => $path,
512:         ));
513:     }
514: 
515: 
516: 
517:     /**
518:      * Sends the session cookies.
519:      * @return void
520:      */
521:     private function sendCookie()
522:     {
523:         $cookie = $this->getCookieParams();
524:         $this->getHttpResponse()->setCookie(session_name(), session_id(), $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
525:         $this->getHttpResponse()->setCookie('nette-browser', $_SESSION['__NF']['B'], HttpResponse::BROWSER, $cookie['path'], $cookie['domain']);
526:     }
527: 
528: 
529: 
530:     /********************* backend ****************d*g**/
531: 
532: 
533: 
534:     /**
535:      * @return Nette\Web\IHttpRequest
536:      */
537:     protected function getHttpRequest()
538:     {
539:         return Nette\Environment::getHttpRequest();
540:     }
541: 
542: 
543: 
544:     /**
545:      * @return Nette\Web\IHttpResponse
546:      */
547:     protected function getHttpResponse()
548:     {
549:         return Nette\Environment::getHttpResponse();
550:     }
551: 
552: }
553: 
Nette Framework 0.9.7 API documentation generated by ApiGen 2.3.0