Completed
Push — master ( 43ed01...e90eeb )
by Fabio
07:20
created

THttpSession::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * THttpSession class
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Web
9
 */
10
11
namespace Prado\Web;
12
13
use Prado\Exceptions\TInvalidDataValueException;
14
use Prado\Exceptions\TInvalidOperationException;
15
use Prado\TPropertyValue;
16
use Prado;
17
18
/**
19
 * THttpSession class
20
 *
21
 * THttpSession provides session-level data management and the related configurations.
22
 * To start the session, call {@link open}; to complete and send out session data, call {@link close};
23
 * to destroy the session, call {@link destroy}. If AutoStart is true, then the session
24
 * will be started once the session module is loaded and initialized.
25
 *
26
 * To access data stored in session, use THttpSession like an associative array. For example,
27
 * <code>
28
 *   $session=new THttpSession;
29
 *   $session->open();
30
 *   $value1=$session['name1'];  // get session variable 'name1'
31
 *   $value2=$session['name2'];  // get session variable 'name2'
32
 *   foreach($session as $name=>$value) // traverse all session variables
33
 *   $session['name3']=$value3;  // set session variable 'name3'
34
 * </code>
35
 *
36
 * The following configurations are available for session:
37
 * {@link setAutoStart AutoStart}, {@link setCookieMode CookieMode},
38
 * {@link setSavePath SavePath},
39
 * {@link setUseCustomStorage UseCustomStorage}, {@link setGCProbability GCProbability},
40
 * {@link setTimeout Timeout}.
41
 * See the corresponding setter and getter documentation for more information.
42
 * Note, these properties must be set before the session is started.
43
 *
44
 * THttpSession can be inherited with customized session storage method.
45
 * Override {@link _open}, {@link _close}, {@link _read}, {@link _write}, {@link _destroy} and {@link _gc}
46
 * and set {@link setUseCustomStorage UseCustomStorage} to true.
47
 * Then, the session data will be stored using the above methods.
48
 *
49
 * By default, THttpSession is registered with {@link TApplication} as the
50
 * request module. It can be accessed via {@link TApplication::getSession()}.
51
 *
52
 * THttpSession may be configured in application configuration file as follows,
53
 * <code>
54
 * <module id="session" class="THttpSession" SessionName="SSID" SavePath="/tmp"
55
 *         CookieMode="Allow" UseCustomStorage="false" AutoStart="true" GCProbability="1"
56
 *         UseTransparentSessionID="true" TimeOut="3600" />
57
 * </code>
58
 * where {@link getSessionName SessionName}, {@link getSavePath SavePath},
59
 * {@link getCookieMode CookieMode}, {@link getUseCustomStorage
60
 * UseCustomStorage}, {@link getAutoStart AutoStart}, {@link getGCProbability
61
 * GCProbability}, {@link getUseTransparentSessionID UseTransparentSessionID}
62
 * and {@link getTimeout TimeOut} are configurable properties of THttpSession.
63
 *
64
 * To avoid the possibility of identity theft through some variants of XSS attacks,
65
 * THttpSessionshould always be configured to enforce HttpOnly setting on session cookie.
66
 * The HttpOnly setting is disabled by default. To enable it, configure the THttpSession
67
 * module as follows,
68
 * <code>
69
 * <module id="session" class="THttpSession" Cookie.HttpOnly="true" >
70
 * </code>
71
 *
72
 * @author Qiang Xue <[email protected]>
73
 * @package Prado\Web
74
 * @since 3.0
75
 */
76
class THttpSession extends \Prado\TApplicationComponent implements \IteratorAggregate, \ArrayAccess, \Countable, \Prado\IModule
77
{
78
	/**
79
	 * @var bool whether this module has been initialized
80
	 */
81
	private $_initialized = false;
82
	/**
83
	 * @var bool whether the session has started
84
	 */
85
	private $_started = false;
86
	/**
87
	 * @var bool whether the session should be started when the module is initialized
88
	 */
89
	private $_autoStart = false;
90
	/**
91
	 * @var THttpCookie cookie to be used to store session ID and other data
92
	 */
93
	private $_cookie;
94
	/**
95
	 * @var string module id
96
	 */
97
	private $_id;
98
	/**
99
	 * @var bool
100
	 */
101
	private $_customStorage = false;
102
103
	/**
104
	 * @return string id of this module
105
	 */
106
	public function getID()
107
	{
108
		return $this->_id;
109
	}
110
111
	/**
112
	 * @param string $value id of this module
113
	 */
114
	public function setID($value)
115
	{
116
		$this->_id = $value;
117
	}
118
119
	/**
120
	 * Initializes the module.
121
	 * This method is required by IModule.
122
	 * If AutoStart is true, the session will be started.
123
	 * @param TXmlElement $config module configuration
124
	 */
125 10
	public function init($config)
126
	{
127 10
		if ($this->_autoStart) {
128
			$this->open();
129
		}
130 10
		$this->_initialized = true;
131 10
		$this->getApplication()->setSession($this);
132 10
		register_shutdown_function([$this, "close"]);
133 10
	}
134
135
	/**
136
	 * Starts the session if it has not started yet.
137
	 */
138
	public function open()
139
	{
140
		if (!$this->_started) {
141
			if ($this->_customStorage) {
142
				session_set_save_handler([$this, '_open'], [$this, '_close'], [$this, '_read'], [$this, '_write'], [$this, '_destroy'], [$this, '_gc']);
143
			}
144
			if ($this->_cookie !== null) {
145
				session_set_cookie_params($this->_cookie->getExpire(), $this->_cookie->getPath(), $this->_cookie->getDomain(), $this->_cookie->getSecure(), $this->_cookie->getHttpOnly());
146
			}
147
			if (ini_get('session.auto_start') !== '1') {
148
				session_start();
149
			}
150
			$this->_started = true;
151
		}
152
	}
153
154
	/**
155
	 * Ends the current session and store session data.
156
	 */
157
	public function close()
158
	{
159
		if ($this->_started) {
160
			session_write_close();
161
			$this->_started = false;
162
		}
163
	}
164
165
	/**
166
	 * Destroys all data registered to a session.
167
	 */
168 1
	public function destroy()
169
	{
170 1
		if ($this->_started) {
171
			session_destroy();
172
			$this->_started = false;
173
		}
174 1
	}
175
176
	/**
177
	 * Update the current session id with a newly generated one
178
	 *
179
	 * @param bool $deleteOld Whether to delete the old associated session or not.
180
	 * @return string old session id
181
	 * @link http://php.net/manual/en/function.session-regenerate-id.php
182
	 */
183
	public function regenerate($deleteOld = false)
184
	{
185
		$old = $this->getSessionID();
186
		session_regenerate_id($deleteOld);
187
		return $old;
188
	}
189
190
	/**
191
	 * @return bool whether the session has started
192
	 */
193 1
	public function getIsStarted()
194
	{
195 1
		return $this->_started;
196
	}
197
198
	/**
199
	 * @return string the current session ID
200
	 */
201
	public function getSessionID()
202
	{
203
		return session_id();
204
	}
205
206
	/**
207
	 * @param string $value the session ID for the current session
208
	 * @throws TInvalidOperationException if session is started already
209
	 */
210
	public function setSessionID($value)
211
	{
212
		if ($this->_started) {
213
			throw new TInvalidOperationException('httpsession_sessionid_unchangeable');
214
		} else {
215
			session_id($value);
216
		}
217
	}
218
219
	/**
220
	 * @return string the current session name
221
	 */
222
	public function getSessionName()
223
	{
224
		return session_name();
225
	}
226
227
	/**
228
	 * @param string $value the session name for the current session, must be an alphanumeric string, defaults to PHPSESSID
229
	 * @throws TInvalidOperationException if session is started already
230
	 */
231
	public function setSessionName($value)
232
	{
233
		if ($this->_started) {
234
			throw new TInvalidOperationException('httpsession_sessionname_unchangeable');
235
		} elseif (ctype_alnum($value)) {
236
			session_name($value);
237
		} else {
238
			throw new TInvalidDataValueException('httpsession_sessionname_invalid', $value);
239
		}
240
	}
241
242
	/**
243
	 * @return string the current session save path, defaults to '/tmp'.
244
	 */
245
	public function getSavePath()
246
	{
247
		return session_save_path();
248
	}
249
250
	/**
251
	 * @param string $value the current session save path
252
	 * @throws TInvalidOperationException if session is started already
253
	 */
254
	public function setSavePath($value)
255
	{
256
		if ($this->_started) {
257
			throw new TInvalidOperationException('httpsession_savepath_unchangeable');
258
		} elseif (is_dir($value)) {
259
			session_save_path(realpath($value));
260
		} elseif (null !== ($ns = Prado::getPathOfNamespace($value)) && is_dir($ns)) {
261
			session_save_path(realpath($ns));
262
		} else {
263
			throw new TInvalidDataValueException('httpsession_savepath_invalid', $value);
264
		}
265
	}
266
267
	/**
268
	 * @return bool whether to use user-specified handlers to store session data. Defaults to false.
269
	 */
270
	public function getUseCustomStorage()
271
	{
272
		return $this->_customStorage;
273
	}
274
275
	/**
276
	 * @param bool $value whether to use user-specified handlers to store session data.
277
	 * If true, make sure the methods {@link _open}, {@link _close}, {@link _read},
278
	 * {@link _write}, {@link _destroy}, and {@link _gc} are overridden in child
279
	 * class, because they will be used as the callback handlers.
280
	 */
281 10
	public function setUseCustomStorage($value)
282
	{
283 10
		$this->_customStorage = TPropertyValue::ensureBoolean($value);
284 10
	}
285
286
	/**
287
	 * @return THttpCookie cookie that will be used to store session ID
288
	 */
289
	public function getCookie()
290
	{
291
		if ($this->_cookie === null) {
292
			$this->_cookie = new THttpCookie($this->getSessionName(), $this->getSessionID());
293
		}
294
		return $this->_cookie;
295
	}
296
297
	/**
298
	 * @return THttpSessionCookieMode how to use cookie to store session ID. Defaults to THttpSessionCookieMode::Allow.
299
	 */
300 3
	public function getCookieMode()
301
	{
302 3
		if (ini_get('session.use_cookies') === '0') {
303 1
			return THttpSessionCookieMode::None;
304 2
		} elseif (ini_get('session.use_only_cookies') === '0') {
305 1
			return THttpSessionCookieMode::Allow;
306
		} else {
307 1
			return THttpSessionCookieMode::Only;
308
		}
309
	}
310
311
	/**
312
	 * @param THttpSessionCookieMode $value how to use cookie to store session ID
313
	 * @throws TInvalidOperationException if session is started already
314
	 */
315 3
	public function setCookieMode($value)
316
	{
317 3
		if ($this->_started) {
318
			throw new TInvalidOperationException('httpsession_cookiemode_unchangeable');
319
		} else {
320 3
			$value = TPropertyValue::ensureEnum($value, 'Prado\\Web\\THttpSessionCookieMode');
321 3
			if ($value === THttpSessionCookieMode::None) {
322 1
				ini_set('session.use_cookies', '0');
323 1
				ini_set('session.use_only_cookies', '0');
324 2
			} elseif ($value === THttpSessionCookieMode::Allow) {
325 1
				ini_set('session.use_cookies', '1');
326 1
				ini_set('session.use_only_cookies', '0');
327
			} else {
328 1
				ini_set('session.use_cookies', '1');
329 1
				ini_set('session.use_only_cookies', '1');
330 1
				ini_set('session.use_trans_sid', 0);
331
			}
332
		}
333 3
	}
334
335
	/**
336
	 * @return bool whether the session should be automatically started when the session module is initialized, defaults to false.
337
	 */
338
	public function getAutoStart()
339
	{
340
		return $this->_autoStart;
341
	}
342
343
	/**
344
	 * @param bool $value whether the session should be automatically started when the session module is initialized, defaults to false.
345
	 * @throws TInvalidOperationException if session is started already
346
	 */
347
	public function setAutoStart($value)
348
	{
349
		if ($this->_initialized) {
350
			throw new TInvalidOperationException('httpsession_autostart_unchangeable');
351
		} else {
352
			$this->_autoStart = TPropertyValue::ensureBoolean($value);
353
		}
354
	}
355
356
	/**
357
	 * @return int the probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
358
	 */
359
	public function getGCProbability()
360
	{
361
		return TPropertyValue::ensureInteger(ini_get('session.gc_probability'));
362
	}
363
364
	/**
365
	 * @param int $value the probability (percentage) that the gc (garbage collection) process is started on every session initialization.
366
	 * @throws TInvalidOperationException if session is started already
367
	 * @throws TInvalidDataValueException if the value is beyond [0,100].
368
	 */
369
	public function setGCProbability($value)
370
	{
371
		if ($this->_started) {
372
			throw new TInvalidOperationException('httpsession_gcprobability_unchangeable');
373
		} else {
374
			$value = TPropertyValue::ensureInteger($value);
375
			if ($value >= 0 && $value <= 100) {
376
				ini_set('session.gc_probability', $value);
377
				ini_set('session.gc_divisor', '100');
378
			} else {
379
				throw new TInvalidDataValueException('httpsession_gcprobability_invalid', $value);
380
			}
381
		}
382
	}
383
384
	/**
385
	 * @return bool whether transparent sid support is enabled or not, defaults to false.
386
	 */
387
	public function getUseTransparentSessionID()
388
	{
389
		return ini_get('session.use_trans_sid') === '1';
390
	}
391
392
	/**
393
	 * Ensure that {@link setCookieMode CookieMode} is not set to "None" before enabling
394
	 * the use of transparent session ids. Refer to the main documentation of the class
395
	 * THttpSession class for a configuration example.
396
	 *
397
	 * @param bool $value whether transparent sid support is enabled or not.
398
	 */
399
	public function setUseTransparentSessionID($value)
400
	{
401
		if ($this->_started) {
402
			throw new TInvalidOperationException('httpsession_transid_unchangeable');
403
		} else {
404
			$value = TPropertyValue::ensureBoolean($value);
405
			if ($value && $this->getCookieMode() == THttpSessionCookieMode::Only) {
406
				throw new TInvalidOperationException('httpsession_transid_cookieonly');
407
			}
408
			ini_set('session.use_trans_sid', $value ? '1' : '0');
409
		}
410
	}
411
412
	/**
413
	 * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
414
	 */
415
	public function getTimeout()
416
	{
417
		return TPropertyValue::ensureInteger(ini_get('session.gc_maxlifetime'));
418
	}
419
420
	/**
421
	 * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
422
	 * @throws TInvalidOperationException if session is started already
423
	 */
424
	public function setTimeout($value)
425
	{
426
		if ($this->_started) {
427
			throw new TInvalidOperationException('httpsession_maxlifetime_unchangeable');
428
		} else {
429
			ini_set('session.gc_maxlifetime', $value);
430
		}
431
	}
432
433
	/**
434
	 * Session open handler.
435
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
436
	 * @param string $savePath session save path
437
	 * @param string $sessionName session name
438
	 * @return bool whether session is opened successfully
439
	 */
440
	public function _open($savePath, $sessionName)
441
	{
442
		return true;
443
	}
444
445
	/**
446
	 * Session close handler.
447
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
448
	 * @return bool whether session is closed successfully
449
	 */
450
	public function _close()
451
	{
452
		return true;
453
	}
454
455
	/**
456
	 * Session read handler.
457
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
458
	 * @param string $id session ID
459
	 * @return string the session data
460
	 */
461
	public function _read($id)
462
	{
463
		return '';
464
	}
465
466
	/**
467
	 * Session write handler.
468
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
469
	 * @param string $id session ID
470
	 * @param string $data session data
471
	 * @return bool whether session write is successful
472
	 */
473
	public function _write($id, $data)
474
	{
475
		return true;
476
	}
477
478
	/**
479
	 * Session destroy handler.
480
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
481
	 * @param string $id session ID
482
	 * @return bool whether session is destroyed successfully
483
	 */
484
	public function _destroy($id)
485
	{
486
		return true;
487
	}
488
489
	/**
490
	 * Session GC (garbage collection) handler.
491
	 * This method should be overridden if {@link setUseCustomStorage UseCustomStorage} is set true.
492
	 * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
493
	 * @return bool whether session is GCed successfully
494
	 */
495
	public function _gc($maxLifetime)
496
	{
497
		return true;
498
	}
499
500
	//------ The following methods enable THttpSession to be TMap-like -----
501
502
	/**
503
	 * Returns an iterator for traversing the session variables.
504
	 * This method is required by the interface \IteratorAggregate.
505
	 * @return TSessionIterator an iterator for traversing the session variables.
506
	 */
507
	public function getIterator()
508
	{
509
		return new TSessionIterator;
510
	}
511
512
	/**
513
	 * @return int the number of session variables
514
	 */
515
	public function getCount()
516
	{
517
		return count($_SESSION);
518
	}
519
520
	/**
521
	 * Returns the number of items in the session.
522
	 * This method is required by \Countable interface.
523
	 * @return int number of items in the session.
524
	 */
525
	public function count()
526
	{
527
		return $this->getCount();
528
	}
529
530
	/**
531
	 * @return array the list of session variable names
532
	 */
533
	public function getKeys()
534
	{
535
		return array_keys($_SESSION);
536
	}
537
538
	/**
539
	 * Returns the session variable value with the session variable name.
540
	 * This method is exactly the same as {@link offsetGet}.
541
	 * @param mixed $key the session variable name
542
	 * @return mixed the session variable value, null if no such variable exists
543
	 */
544
	public function itemAt($key)
545
	{
546
		return $_SESSION[$key] ?? null;
547
	}
548
549
	/**
550
	 * Adds a session variable.
551
	 * Note, if the specified name already exists, the old value will be removed first.
552
	 * @param mixed $key session variable name
553
	 * @param mixed $value session variable value
554
	 */
555 1
	public function add($key, $value)
556
	{
557 1
		$_SESSION[$key] = $value;
558 1
	}
559
560
	/**
561
	 * Removes a session variable.
562
	 * @param mixed $key the name of the session variable to be removed
563
	 * @return mixed the removed value, null if no such session variable.
564
	 */
565 1
	public function remove($key)
566
	{
567 1
		if (isset($_SESSION[$key])) {
568 1
			$value = $_SESSION[$key];
569 1
			unset($_SESSION[$key]);
570 1
			return $value;
571
		} else {
572
			return null;
573
		}
574
	}
575
576
	/**
577
	 * Removes all session variables
578
	 */
579
	public function clear()
580
	{
581
		foreach (array_keys($_SESSION) as $key) {
582
			unset($_SESSION[$key]);
583
		}
584
	}
585
586
	/**
587
	 * @param mixed $key session variable name
588
	 * @return bool whether there is the named session variable
589
	 */
590
	public function contains($key)
591
	{
592
		return isset($_SESSION[$key]);
593
	}
594
595
	/**
596
	 * @return array the list of all session variables in array
597
	 */
598
	public function toArray()
599
	{
600
		return $_SESSION;
601
	}
602
603
	/**
604
	 * This method is required by the interface \ArrayAccess.
605
	 * @param mixed $offset the offset to check on
606
	 * @return bool
607
	 */
608
	public function offsetExists($offset)
609
	{
610
		return isset($_SESSION[$offset]);
611
	}
612
613
	/**
614
	 * This method is required by the interface \ArrayAccess.
615
	 * @param int $offset the offset to retrieve element.
616
	 * @return mixed the element at the offset, null if no element is found at the offset
617
	 */
618 4
	public function offsetGet($offset)
619
	{
620 4
		return $_SESSION[$offset] ?? null;
621
	}
622
623
	/**
624
	 * This method is required by the interface \ArrayAccess.
625
	 * @param int $offset the offset to set element
626
	 * @param mixed $item the element value
627
	 */
628 2
	public function offsetSet($offset, $item)
629
	{
630 2
		$_SESSION[$offset] = $item;
631 2
	}
632
633
	/**
634
	 * This method is required by the interface \ArrayAccess.
635
	 * @param mixed $offset the offset to unset element
636
	 */
637
	public function offsetUnset($offset)
638
	{
639
		unset($_SESSION[$offset]);
640
	}
641
}
642