Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy

Classes

  • Context
  • FileUpload
  • Helpers
  • Request
  • RequestFactory
  • Response
  • Session
  • SessionSection
  • Url
  • UrlScript
  • UserStorage

Interfaces

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