Packages

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

Classes

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

Interfaces

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