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