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