1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19:
20: class Session extends Object
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:
71: 72: try {
73: $this->configure($this->options);
74: } catch (NotSupportedException $e) {
75: 76: }
77:
78: Debug::tryError();
79: session_start();
80: if (Debug::catchError($msg)) {
81: @session_write_close(); 82: throw new InvalidStateException($msg);
83: }
84:
85: self::$started = TRUE;
86: if ($this->regenerationNeeded) {
87: session_regenerate_id(TRUE);
88: $this->regenerationNeeded = FALSE;
89: }
90:
91: 92: 93: 94: 95:
96:
97: unset($_SESSION['__NT'], $_SESSION['__NS'], $_SESSION['__NM']); 98:
99: 100: $nf = & $_SESSION['__NF'];
101: if (empty($nf)) { 102: $nf = array('C' => 0);
103: } else {
104: $nf['C']++;
105: }
106:
107: 108: $browserKey = $this->getHttpRequest()->getCookie('nette-browser');
109: if (!$browserKey) {
110: $browserKey = (string) lcg_value();
111: }
112: $browserClosed = !isset($nf['B']) || $nf['B'] !== $browserKey;
113: $nf['B'] = $browserKey;
114:
115: 116: $this->sendCookie();
117:
118: 119: if (isset($nf['META'])) {
120: $now = time();
121: 122: foreach ($nf['META'] as $namespace => $metadata) {
123: if (is_array($metadata)) {
124: foreach ($metadata as $variable => $value) {
125: if ((!empty($value['B']) && $browserClosed) || (!empty($value['T']) && $now > $value['T']) 126: || ($variable !== '' && is_object($nf['DATA'][$namespace][$variable]) && (isset($value['V']) ? $value['V'] : NULL) 127: !== ClassReflection::from($nf['DATA'][$namespace][$variable])->getAnnotation('serializationVersion'))) {
128:
129: if ($variable === '') { 130: unset($nf['META'][$namespace], $nf['DATA'][$namespace]);
131: continue 2;
132: }
133: unset($nf['META'][$namespace][$variable], $nf['DATA'][$namespace][$variable]);
134: }
135: }
136: }
137: }
138: }
139:
140: register_shutdown_function(array($this, 'clean'));
141: }
142:
143:
144:
145: 146: 147: 148:
149: public function isStarted()
150: {
151: return (bool) self::$started;
152: }
153:
154:
155:
156: 157: 158: 159:
160: public function close()
161: {
162: if (self::$started) {
163: $this->clean();
164: session_write_close();
165: self::$started = FALSE;
166: }
167: }
168:
169:
170:
171: 172: 173: 174:
175: public function destroy()
176: {
177: if (!self::$started) {
178: throw new InvalidStateException('Session is not started.');
179: }
180:
181: session_destroy();
182: $_SESSION = NULL;
183: self::$started = FALSE;
184: if (!$this->getHttpResponse()->isSent()) {
185: $params = session_get_cookie_params();
186: $this->getHttpResponse()->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
187: }
188: }
189:
190:
191:
192: 193: 194: 195:
196: public function exists()
197: {
198: return self::$started || $this->getHttpRequest()->getCookie(session_name()) !== NULL;
199: }
200:
201:
202:
203: 204: 205: 206: 207:
208: public function regenerateId()
209: {
210: if (self::$started) {
211: if (headers_sent($file, $line)) {
212: throw new InvalidStateException("Cannot regenerate session ID after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
213: }
214: session_regenerate_id(TRUE);
215:
216: } else {
217: $this->regenerationNeeded = TRUE;
218: }
219: }
220:
221:
222:
223: 224: 225: 226:
227: public function getId()
228: {
229: return session_id();
230: }
231:
232:
233:
234: 235: 236: 237: 238:
239: public function setName($name)
240: {
241: if (!is_string($name) || !preg_match('#[^0-9.][^.]*$#A', $name)) {
242: throw new InvalidArgumentException('Session name must be a string and cannot contain dot.');
243: }
244:
245: session_name($name);
246: return $this->setOptions(array(
247: 'name' => $name,
248: ));
249: }
250:
251:
252:
253: 254: 255: 256:
257: public function getName()
258: {
259: return session_name();
260: }
261:
262:
263:
264:
265:
266:
267:
268: 269: 270: 271: 272: 273: 274:
275: public function getNamespace($namespace, $class = 'SessionNamespace')
276: {
277: if (!is_string($namespace) || $namespace === '') {
278: throw new InvalidArgumentException('Session namespace must be a non-empty string.');
279: }
280:
281: if (!self::$started) {
282: $this->start();
283: }
284:
285: return new $class($_SESSION['__NF']['DATA'][$namespace], $_SESSION['__NF']['META'][$namespace]);
286: }
287:
288:
289:
290: 291: 292: 293: 294:
295: public function hasNamespace($namespace)
296: {
297: if ($this->exists() && !self::$started) {
298: $this->start();
299: }
300:
301: return !empty($_SESSION['__NF']['DATA'][$namespace]);
302: }
303:
304:
305:
306: 307: 308: 309:
310: public function getIterator()
311: {
312: if ($this->exists() && !self::$started) {
313: $this->start();
314: }
315:
316: if (isset($_SESSION['__NF']['DATA'])) {
317: return new ArrayIterator(array_keys($_SESSION['__NF']['DATA']));
318:
319: } else {
320: return new ArrayIterator;
321: }
322: }
323:
324:
325:
326: 327: 328: 329:
330: public function clean()
331: {
332: if (!self::$started || empty($_SESSION)) {
333: return;
334: }
335:
336: $nf = & $_SESSION['__NF'];
337: if (isset($nf['META']) && is_array($nf['META'])) {
338: foreach ($nf['META'] as $name => $foo) {
339: if (empty($nf['META'][$name])) {
340: unset($nf['META'][$name]);
341: }
342: }
343: }
344:
345: if (empty($nf['META'])) {
346: unset($nf['META']);
347: }
348:
349: if (empty($nf['DATA'])) {
350: unset($nf['DATA']);
351: }
352:
353: if (empty($_SESSION)) {
354: 355: }
356: }
357:
358:
359:
360:
361:
362:
363:
364: 365: 366: 367: 368: 369: 370:
371: public function setOptions(array $options)
372: {
373: if (self::$started) {
374: $this->configure($options);
375: }
376: $this->options = $options + $this->options;
377: return $this;
378: }
379:
380:
381:
382: 383: 384: 385:
386: public function getOptions()
387: {
388: return $this->options;
389: }
390:
391:
392:
393: 394: 395: 396: 397:
398: private function configure(array $config)
399: {
400: $special = array('cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1);
401:
402: foreach ($config as $key => $value) {
403: if (!strncmp($key, 'session.', 8)) { 404: $key = substr($key, 8);
405: }
406:
407: if ($value === NULL) {
408: continue;
409:
410: } elseif (isset($special[$key])) {
411: if (self::$started) {
412: throw new InvalidStateException("Unable to set '$key' when session has been started.");
413: }
414: $key = "session_$key";
415: $key($value);
416:
417: } elseif (strncmp($key, 'cookie_', 7) === 0) {
418: if (!isset($cookie)) {
419: $cookie = session_get_cookie_params();
420: }
421: $cookie[substr($key, 7)] = $value;
422:
423: } elseif (!function_exists('ini_set')) {
424: if (ini_get($key) != $value) { 425: throw new NotSupportedException('Required function ini_set() is disabled.');
426: }
427:
428: } else {
429: if (self::$started) {
430: throw new InvalidStateException("Unable to set '$key' when session has been started.");
431: }
432: ini_set("session.$key", $value);
433: }
434: }
435:
436: if (isset($cookie)) {
437: session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
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 = Tools::createDateTime($time)->format('U');
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: private function sendCookie()
517: {
518: $cookie = $this->getCookieParams();
519: $this->getHttpResponse()->setCookie(session_name(), session_id(), $cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
520: $this->getHttpResponse()->setCookie('nette-browser', $_SESSION['__NF']['B'], HttpResponse::BROWSER, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
521: }
522:
523:
524:
525:
526:
527:
528:
529: 530: 531:
532: protected function getHttpRequest()
533: {
534: return Environment::getHttpRequest();
535: }
536:
537:
538:
539: 540: 541:
542: protected function getHttpResponse()
543: {
544: return Environment::getHttpResponse();
545: }
546:
547: }
548: