Namespaces

  • 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

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