1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Web;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22:
23: class Session extends Nette\Object
24: {
25:
26: const DEFAULT_FILE_LIFETIME = 10800;
27:
28:
29: public $verificationKeyGenerator;
30:
31:
32: private $regenerationNeeded;
33:
34:
35: private static $started;
36:
37:
38: private $options = array(
39:
40: 'referer_check' => '',
41: 'use_cookies' => 1,
42: 'use_only_cookies' => 1,
43: 'use_trans_sid' => 0,
44:
45:
46: 'cookie_lifetime' => 0,
47: 'cookie_path' => '/',
48: 'cookie_domain' => '',
49: 'cookie_secure' => FALSE,
50: 'cookie_httponly' => TRUE,
51:
52:
53: 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
54: 'cache_limiter' => NULL,
55: 'cache_expire' => NULL,
56: 'hash_function' => NULL,
57: 'hash_bits_per_character' => NULL,
58: );
59:
60:
61:
62: 63: 64: 65: 66:
67: public function start()
68: {
69: if (self::$started) {
70: return;
71: }
72:
73:
74:
75: try {
76: $this->configure($this->options);
77: } catch (\NotSupportedException $e) {
78:
79: }
80:
81: if (!defined('SID')) {
82: Nette\Tools::tryError();
83: session_start();
84: if (Nette\Tools::catchError($msg)) {
85: @session_write_close();
86: throw new \InvalidStateException($msg);
87: }
88: }
89:
90: self::$started = TRUE;
91: if ($this->regenerationNeeded) {
92: session_regenerate_id(TRUE);
93: $this->regenerationNeeded = FALSE;
94: }
95:
96: 97: 98: 99: 100:
101:
102: unset($_SESSION['__NT'], $_SESSION['__NS'], $_SESSION['__NM']);
103:
104:
105: $nf = & $_SESSION['__NF'];
106: if (empty($nf)) {
107: $nf = array('C' => 0);
108: } else {
109: $nf['C']++;
110: }
111:
112:
113: $browserKey = $this->getHttpRequest()->getCookie('nette-browser');
114: if (!$browserKey) {
115: $browserKey = (string) lcg_value();
116: }
117: $browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
118: $nf['B'] = $browserKey;
119:
120:
121: $this->sendCookie();
122:
123:
124: if (isset($nf['META'])) {
125: $now = time();
126:
127: foreach ($nf['META'] as $namespace => $metadata) {
128: if (is_array($metadata)) {
129: foreach ($metadata as $variable => $value) {
130: if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T'])
131: || ($variable !== '' && is_object($nf['DATA'][$namespace][$variable]) && (isset($value['V']) ? $value['V'] : NULL)
132: !== Nette\Reflection\ClassReflection::from($nf['DATA'][$namespace][$variable])->getAnnotation('serializationVersion'))) {
133:
134: if ($variable === '') {
135: unset($nf['META'][$namespace], $nf['DATA'][$namespace]);
136: continue 2;
137: }
138: unset($nf['META'][$namespace][$variable], $nf['DATA'][$namespace][$variable]);
139: }
140: }
141: }
142: }
143: }
144:
145: register_shutdown_function(array($this, 'clean'));
146: }
147:
148:
149:
150: 151: 152: 153:
154: public function isStarted()
155: {
156: return (bool) self::$started;
157: }
158:
159:
160:
161: 162: 163: 164:
165: public function close()
166: {
167: if (self::$started) {
168: $this->clean();
169: session_write_close();
170: self::$started = FALSE;
171: }
172: }
173:
174:
175:
176: 177: 178: 179:
180: public function destroy()
181: {
182: if (!self::$started) {
183: throw new \InvalidStateException('Session is not started.');
184: }
185:
186: session_destroy();
187: $_SESSION = NULL;
188: self::$started = FALSE;
189: if (!$this->getHttpResponse()->isSent()) {
190: $params = session_get_cookie_params();
191: $this->getHttpResponse()->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
192: }
193: }
194:
195:
196:
197: 198: 199: 200:
201: public function exists()
202: {
203: return self::$started || $this->getHttpRequest()->getCookie(session_name()) !== NULL;
204: }
205:
206:
207:
208: 209: 210: 211: 212:
213: public function regenerateId()
214: {
215: if (self::$started) {
216: if (headers_sent($file, $line)) {
217: throw new \InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
218: }
219: session_regenerate_id(TRUE);
220:
221: } else {
222: $this->regenerationNeeded = TRUE;
223: }
224: }
225:
226:
227:
228: 229: 230: 231:
232: public function getId()
233: {
234: return session_id();
235: }
236:
237:
238:
239: 240: 241: 242: 243:
244: public function setName($name)
245: {
246: if (!is_string($name) || !preg_match('#[^0-9.][^.]*$#A', $name)) {
247: throw new \InvalidArgumentException('Session name must be a string and cannot contain dot.');
248: }
249:
250: session_name($name);
251: return $this->setOptions(array(
252: 'name' => $name,
253: ));
254: }
255:
256:
257:
258: 259: 260: 261:
262: public function getName()
263: {
264: return session_name();
265: }
266:
267:
268:
269:
270:
271:
272:
273: 274: 275: 276: 277: 278: 279:
280: public function getNamespace($namespace, $class = 'Nette\Web\SessionNamespace')
281: {
282: if (!is_string($namespace) || $namespace === '') {
283: throw new \InvalidArgumentException('Session namespace must be a non-empty string.');
284: }
285:
286: if (!self::$started) {
287: $this->start();
288: }
289:
290: return new $class($_SESSION['__NF']['DATA'][$namespace], $_SESSION['__NF']['META'][$namespace]);
291: }
292:
293:
294:
295: 296: 297: 298: 299:
300: public function hasNamespace($namespace)
301: {
302: if ($this->exists() && !self::$started) {
303: $this->start();
304: }
305:
306: return !empty($_SESSION['__NF']['DATA'][$namespace]);
307: }
308:
309:
310:
311: 312: 313: 314:
315: public function getIterator()
316: {
317: if ($this->exists() && !self::$started) {
318: $this->start();
319: }
320:
321: if (isset($_SESSION['__NF']['DATA'])) {
322: return new \ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
323:
324: } else {
325: return new \ArrayIterator;
326: }
327: }
328:
329:
330:
331: 332: 333: 334:
335: public function clean()
336: {
337: if (!self::$started || empty($_SESSION)) {
338: return;
339: }
340:
341: $nf = & $_SESSION['__NF'];
342: if (isset($nf['META']) && is_array($nf['META'])) {
343: foreach ($nf['META'] as $name => $foo) {
344: if (empty($nf['META'][$name])) {
345: unset($nf['META'][$name]);
346: }
347: }
348: }
349:
350: if (empty($nf['META'])) {
351: unset($nf['META']);
352: }
353:
354: if (empty($nf['DATA'])) {
355: unset($nf['DATA']);
356: }
357:
358: if (empty($_SESSION)) {
359:
360: }
361: }
362:
363:
364:
365:
366:
367:
368:
369: 370: 371: 372: 373: 374: 375:
376: public function setOptions(array $options)
377: {
378: if (self::$started) {
379: $this->configure($options);
380: }
381: $this->options = $options + $this->options;
382: if (!empty($options['auto_start'])) {
383: $this->start();
384: }
385: return $this;
386: }
387:
388:
389:
390: 391: 392: 393:
394: public function getOptions()
395: {
396: return $this->options;
397: }
398:
399:
400:
401: 402: 403: 404: 405:
406: private function configure(array $config)
407: {
408: $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
409:
410: foreach ($config as $key => $value) {
411: if (!strncmp($key, 'session.', 8)) {
412: $key = substr($key, 8);
413: }
414:
415: if ($value === NULL || ini_get("session.$key") == $value) {
416: continue;
417:
418: } elseif (strncmp($key, 'cookie_', 7) === 0) {
419: if (!isset($cookie)) {
420: $cookie = session_get_cookie_params();
421: }
422: $cookie[substr($key, 7)] = $value;
423:
424: } else {
425: if (defined('SID')) {
426: throw new \InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started by session.auto_start or session_start().");
427: }
428: if (isset($special[$key])) {
429: $key = "session_$key";
430: $key($value);
431:
432: } elseif (function_exists('ini_set')) {
433: ini_set("session.$key", $value);
434:
435: } else {
436:
437: }
438: }
439: }
440:
441: if (isset($cookie)) {
442: session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
443: if (self::$started) {
444: $this->sendCookie();
445: }
446: }
447: }
448:
449:
450:
451: 452: 453: 454: 455:
456: public function setExpiration($time)
457: {
458: if (empty($time)) {
459: return $this->setOptions(array(
460: 'gc_maxlifetime' => self::DEFAULT_FILE_LIFETIME,
461: 'cookie_lifetime' => 0,
462: ));
463:
464: } else {
465: $time = Nette\DateTime::from($time)->format('U') - time();
466: return $this->setOptions(array(
467: 'gc_maxlifetime' => $time,
468: 'cookie_lifetime' => $time,
469: ));
470: }
471: }
472:
473:
474:
475: 476: 477: 478: 479: 480: 481:
482: public function setCookieParams($path, $domain = NULL, $secure = NULL)
483: {
484: return $this->setOptions(array(
485: 'cookie_path' => $path,
486: 'cookie_domain' => $domain,
487: 'cookie_secure' => $secure
488: ));
489: }
490:
491:
492:
493: 494: 495: 496:
497: public function getCookieParams()
498: {
499: return session_get_cookie_params();
500: }
501:
502:
503:
504: 505: 506: 507:
508: public function setSavePath($path)
509: {
510: return $this->setOptions(array(
511: 'save_path' => $path,
512: ));
513: }
514:
515:
516:
517: 518: 519: 520:
521: private function sendCookie()
522: {
523: $cookie = $this->getCookieParams();
524: $this->getHttpResponse()->setCookie(session_name(), session_id(), $cookie['lifetime'] ? $cookie['lifetime'] + time() : 0, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
525: $this->getHttpResponse()->setCookie('nette-browser', $_SESSION['__NF']['B'], HttpResponse::BROWSER, $cookie['path'], $cookie['domain']);
526: }
527:
528:
529:
530:
531:
532:
533:
534: 535: 536:
537: protected function getHttpRequest()
538: {
539: return Nette\Environment::getHttpRequest();
540: }
541:
542:
543:
544: 545: 546:
547: protected function getHttpResponse()
548: {
549: return Nette\Environment::getHttpResponse();
550: }
551:
552: }
553: