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