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