Namespaces

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

Classes

  • Context
  • FileUpload
  • Request
  • RequestFactory
  • Response
  • Session
  • SessionSection
  • Url
  • UrlScript
  • User

Interfaces

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