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