Packages

  • Nette
    • Application
      • Application\Diagnostics
      • Application\Responses
      • Application\Routers
      • Application\UI
    • Caching
      • Caching\Storages
    • ComponentModel
    • Config
    • Database
      • Database\Diagnostics
      • Database\Drivers
      • Database\Reflection
      • Database\Table
    • DI
    • Diagnostics
    • Forms
      • Forms\Controls
      • Forms\Rendering
    • Http
    • Iterators
    • Latte
      • Latte\Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • None
  • PHP

Classes

  • HttpContext
  • HttpRequest
  • HttpRequestFactory
  • HttpResponse
  • HttpUploadedFile
  • Session
  • SessionSection
  • Url
  • UrlScript
  • User

Interfaces

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