Merge pull request #15840 from yashodha/participant_edit
[civicrm-core.git] / CRM / Core / Session.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035 11
4c6ce474 12/**
8eedd10a 13 * Class CRM_Core_Session.
4c6ce474 14 */
6a488035
TO
15class CRM_Core_Session {
16
17 /**
f9e31d7f 18 * Cache of all the session names that we manage.
518fa0ee 19 * @var array
6a488035 20 */
518fa0ee 21 public static $_managedNames = NULL;
6a488035
TO
22
23 /**
100fef9d 24 * Key is used to allow the application to have multiple top
6a488035
TO
25 * level scopes rather than a single scope. (avoids naming
26 * conflicts). We also extend this idea further and have local
27 * scopes within a global scope. Allows us to do cool things
28 * like resetting a specific area of the session code while
29 * keeping the rest intact
30 *
31 * @var string
32 */
33 protected $_key = 'CiviCRM';
7da04cde 34 const USER_CONTEXT = 'userContext';
6a488035
TO
35
36 /**
37 * This is just a reference to the real session. Allows us to
38 * debug this class a wee bit easier
39 *
40 * @var object
41 */
42 protected $_session = NULL;
43
44 /**
45 * We only need one instance of this object. So we use the singleton
46 * pattern and cache the instance in this variable
47 *
48 * @var object
6a488035
TO
49 */
50 static private $_singleton = NULL;
51
52 /**
f9e31d7f 53 * Constructor.
6a488035 54 *
d4ad6ab3 55 * The CMS takes care of initiating the php session handler session_start().
6a488035 56 *
6a488035 57 * All crm code should always use the session using
d4ad6ab3
CW
58 * CRM_Core_Session. we prefix stuff to avoid collisions with the CMS and also
59 * collisions with other crm modules!
60 *
6a488035
TO
61 * This constructor is invoked whenever any module requests an instance of
62 * the session and one is not available.
63 *
d4ad6ab3 64 * @return CRM_Core_Session
6a488035 65 */
00be9182 66 public function __construct() {
2aa397bc 67 $this->_session = NULL;
6a488035
TO
68 }
69
70 /**
f9e31d7f 71 * Singleton function used to manage this object.
6a488035 72 *
d46561f5 73 * @return CRM_Core_Session
6a488035 74 */
00be9182 75 public static function &singleton() {
6a488035 76 if (self::$_singleton === NULL) {
317fceb4 77 self::$_singleton = new CRM_Core_Session();
6a488035
TO
78 }
79 return self::$_singleton;
80 }
81
82 /**
8eedd10a 83 * Creates an array in the session.
84 *
85 * All variables now will be stored under this array.
6a488035 86 *
6a0b768e
TO
87 * @param bool $isRead
88 * Is this a read operation, in this case, the session will not be touched.
6a488035 89 */
00be9182 90 public function initialize($isRead = FALSE) {
6a488035
TO
91 // lets initialize the _session variable just before we need it
92 // hopefully any bootstrapping code will actually load the session from the CMS
93 if (!isset($this->_session)) {
94 // CRM-9483
95 if (!isset($_SESSION) && PHP_SAPI !== 'cli') {
96 if ($isRead) {
97 return;
98 }
d4ad6ab3 99 // FIXME: This belongs in CRM_Utils_System_*
cae540ac 100 if (CRM_Core_Config::singleton()->userSystem->is_drupal && function_exists('drupal_session_start')) {
2c85a284 101 // https://issues.civicrm.org/jira/browse/CRM-14356
353ffa53 102 if (!(isset($GLOBALS['lazy_session']) && $GLOBALS['lazy_session'] == TRUE)) {
af730db2
TO
103 drupal_session_start();
104 }
be2fb01f 105 $_SESSION = [];
6a488035
TO
106 }
107 else {
108 session_start();
109 }
110 }
111 $this->_session =& $_SESSION;
112 }
113
114 if ($isRead) {
115 return;
116 }
117
118 if (!isset($this->_session[$this->_key]) ||
119 !is_array($this->_session[$this->_key])
120 ) {
be2fb01f 121 $this->_session[$this->_key] = [];
6a488035 122 }
6a488035
TO
123 }
124
125 /**
f9e31d7f 126 * Resets the session store.
6a488035 127 *
dd244018 128 * @param int $all
6a488035 129 */
00be9182 130 public function reset($all = 1) {
6a488035
TO
131 if ($all != 1) {
132 $this->initialize();
133
134 // to make certain we clear it, first initialize it to empty
be2fb01f 135 $this->_session[$this->_key] = [];
6a488035
TO
136 unset($this->_session[$this->_key]);
137 }
138 else {
be2fb01f 139 $this->_session = [];
6a488035
TO
140 }
141
6a488035
TO
142 }
143
144 /**
f9e31d7f 145 * Creates a session local scope.
6a488035 146 *
6a0b768e
TO
147 * @param string $prefix
148 * Local scope name.
149 * @param bool $isRead
150 * Is this a read operation, in this case, the session will not be touched.
6a488035 151 */
00be9182 152 public function createScope($prefix, $isRead = FALSE) {
6a488035
TO
153 $this->initialize($isRead);
154
155 if ($isRead || empty($prefix)) {
156 return;
157 }
158
a7488080 159 if (empty($this->_session[$this->_key][$prefix])) {
be2fb01f 160 $this->_session[$this->_key][$prefix] = [];
6a488035
TO
161 }
162 }
163
164 /**
f9e31d7f 165 * Resets the session local scope.
6a488035 166 *
6a0b768e
TO
167 * @param string $prefix
168 * Local scope name.
6a488035 169 */
00be9182 170 public function resetScope($prefix) {
6a488035
TO
171 $this->initialize();
172
173 if (empty($prefix)) {
174 return;
175 }
176
177 if (array_key_exists($prefix, $this->_session[$this->_key])) {
178 unset($this->_session[$this->_key][$prefix]);
179 }
180 }
181
182 /**
f9e31d7f 183 * Store the variable with the value in the session scope.
6a488035
TO
184 *
185 * This function takes a name, value pair and stores this
186 * in the session scope. Not sure what happens if we try
187 * to store complex objects in the session. I suspect it
188 * is supported but we need to verify this
189 *
6a488035 190 *
6a0b768e
TO
191 * @param string $name
192 * Name of the variable.
193 * @param mixed $value
194 * Value of the variable.
195 * @param string $prefix
196 * A string to prefix the keys in the session with.
6a488035 197 */
00be9182 198 public function set($name, $value = NULL, $prefix = NULL) {
6a488035
TO
199 // create session scope
200 $this->createScope($prefix);
201
202 if (empty($prefix)) {
203 $session = &$this->_session[$this->_key];
204 }
205 else {
206 $session = &$this->_session[$this->_key][$prefix];
207 }
208
209 if (is_array($name)) {
210 foreach ($name as $n => $v) {
211 $session[$n] = $v;
212 }
213 }
214 else {
215 $session[$name] = $value;
216 }
217 }
218
219 /**
f9e31d7f 220 * Gets the value of the named variable in the session scope.
6a488035
TO
221 *
222 * This function takes a name and retrieves the value of this
223 * variable from the session scope.
224 *
6a488035 225 *
6a0b768e 226 * @param string $name
16b10e64 227 * name of the variable.
6a0b768e 228 * @param string $prefix
16b10e64 229 * adds another level of scope to the session.
6a488035
TO
230 *
231 * @return mixed
6a488035 232 */
00be9182 233 public function get($name, $prefix = NULL) {
6a488035
TO
234 // create session scope
235 $this->createScope($prefix, TRUE);
236
237 if (empty($this->_session) || empty($this->_session[$this->_key])) {
2aa397bc 238 return NULL;
6a488035
TO
239 }
240
241 if (empty($prefix)) {
242 $session =& $this->_session[$this->_key];
243 }
244 else {
245 if (empty($this->_session[$this->_key][$prefix])) {
2aa397bc 246 return NULL;
6a488035
TO
247 }
248 $session =& $this->_session[$this->_key][$prefix];
249 }
250
251 return CRM_Utils_Array::value($name, $session);
252 }
253
254 /**
8eedd10a 255 * Gets all the variables in the current session scope and stuffs them in an associate array.
6a488035 256 *
6a0b768e
TO
257 * @param array $vars
258 * Associative array to store name/value pairs.
259 * @param string $prefix
260 * Will be stripped from the key before putting it in the return.
6a488035 261 */
00be9182 262 public function getVars(&$vars, $prefix = '') {
6a488035
TO
263 // create session scope
264 $this->createScope($prefix, TRUE);
265
266 if (empty($prefix)) {
267 $values = &$this->_session[$this->_key];
268 }
269 else {
19707a63 270 $values = Civi::cache('session')->get("CiviCRM_{$prefix}");
6a488035
TO
271 }
272
273 if ($values) {
274 foreach ($values as $name => $value) {
275 $vars[$name] = $value;
276 }
277 }
278 }
279
280 /**
8eedd10a 281 * Set and check a timer.
282 *
283 * If it's expired, it will be set again.
284 *
6a488035
TO
285 * Good for showing a message to the user every hour or day (so not bugging them on every page)
286 * Returns true-ish values if the timer is not set or expired, and false if the timer is still running
287 * If you want to get more nuanced, you can check the type of the return to see if it's 'not set' or actually expired at a certain time
288 *
6a488035 289 *
6a0b768e 290 * @param string $name
16b10e64 291 * name of the timer.
6a0b768e 292 * @param int $expire
16b10e64 293 * expiry time (in seconds).
6a488035
TO
294 *
295 * @return mixed
6a488035 296 */
00be9182 297 public function timer($name, $expire) {
6a488035
TO
298 $ts = $this->get($name, 'timer');
299 if (!$ts || $ts < time() - $expire) {
300 $this->set($name, time(), 'timer');
301 return $ts ? $ts : 'not set';
302 }
2aa397bc 303 return FALSE;
6a488035
TO
304 }
305
306 /**
f9e31d7f 307 * Adds a userContext to the stack.
6a488035 308 *
6a0b768e
TO
309 * @param string $userContext
310 * The url to return to when done.
311 * @param bool $check
312 * Should we do a dupe checking with the top element.
6a488035 313 */
00be9182 314 public function pushUserContext($userContext, $check = TRUE) {
6a488035
TO
315 if (empty($userContext)) {
316 return;
317 }
318
319 $this->createScope(self::USER_CONTEXT);
320
321 // hack, reset if too big
322 if (count($this->_session[$this->_key][self::USER_CONTEXT]) > 10) {
323 $this->resetScope(self::USER_CONTEXT);
324 $this->createScope(self::USER_CONTEXT);
325 }
326
327 $topUC = array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
328
329 // see if there is a match between the new UC and the top one. the match needs to be
330 // fuzzy since we use the referer at times
331 // if close enough, lets just replace the top with the new one
332 if ($check && $topUC && CRM_Utils_String::match($topUC, $userContext)) {
333 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
334 }
335 else {
336 if ($topUC) {
337 array_push($this->_session[$this->_key][self::USER_CONTEXT], $topUC);
338 }
339 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
340 }
341 }
342
343 /**
f9e31d7f 344 * Replace the userContext of the stack with the passed one.
6a488035 345 *
6a0b768e
TO
346 * @param string $userContext
347 * The url to return to when done.
6a488035 348 */
00be9182 349 public function replaceUserContext($userContext) {
6a488035
TO
350 if (empty($userContext)) {
351 return;
352 }
353
354 $this->createScope(self::USER_CONTEXT);
355
356 array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
357 array_push($this->_session[$this->_key][self::USER_CONTEXT], $userContext);
358 }
359
360 /**
f9e31d7f 361 * Pops the top userContext stack.
6a488035 362 *
a6c01b45
CW
363 * @return string
364 * the top of the userContext stack (also pops the top element)
6a488035 365 */
00be9182 366 public function popUserContext() {
6a488035
TO
367 $this->createScope(self::USER_CONTEXT);
368
369 return array_pop($this->_session[$this->_key][self::USER_CONTEXT]);
370 }
371
372 /**
f9e31d7f 373 * Reads the top userContext stack.
6a488035 374 *
a6c01b45
CW
375 * @return string
376 * the top of the userContext stack
6a488035 377 */
00be9182 378 public function readUserContext() {
6a488035
TO
379 $this->createScope(self::USER_CONTEXT);
380
381 $config = CRM_Core_Config::singleton();
382 $lastElement = count($this->_session[$this->_key][self::USER_CONTEXT]) - 1;
383 return $lastElement >= 0 ? $this->_session[$this->_key][self::USER_CONTEXT][$lastElement] : $config->userFrameworkBaseURL;
384 }
385
386 /**
f9e31d7f 387 * Dumps the session to the log.
8eedd10a 388 *
d4ad6ab3 389 * @param int $all
6a488035 390 */
00be9182 391 public function debug($all = 1) {
6a488035
TO
392 $this->initialize();
393 if ($all != 1) {
394 CRM_Core_Error::debug('CRM Session', $this->_session);
395 }
396 else {
397 CRM_Core_Error::debug('CRM Session', $this->_session[$this->_key]);
398 }
399 }
400
401 /**
f9e31d7f 402 * Fetches status messages.
6a488035 403 *
6a0b768e
TO
404 * @param bool $reset
405 * Should we reset the status variable?.
6a488035 406 *
a6c01b45
CW
407 * @return string
408 * the status message if any
6a488035 409 */
00be9182 410 public function getStatus($reset = FALSE) {
6a488035
TO
411 $this->initialize();
412
413 $status = NULL;
414 if (array_key_exists('status', $this->_session[$this->_key])) {
415 $status = $this->_session[$this->_key]['status'];
416 }
417 if ($reset) {
418 $this->_session[$this->_key]['status'] = NULL;
419 unset($this->_session[$this->_key]['status']);
420 }
421 return $status;
422 }
423
424 /**
8eedd10a 425 * Stores an alert to be displayed to the user via crm-messages.
6a488035 426 *
5a4f6742 427 * @param string $text
6a488035
TO
428 * The status message
429 *
5a4f6742 430 * @param string $title
6a488035
TO
431 * The optional title of this message
432 *
5a4f6742 433 * @param string $type
6a488035
TO
434 * The type of this message (printed as a css class). Possible options:
435 * - 'alert' (default)
436 * - 'info'
437 * - 'success'
438 * - 'error' (this message type by default will remain on the screen
439 * until the user dismisses it)
440 * - 'no-popup' (will display in the document like old-school)
441 *
5a4f6742 442 * @param array $options
6a488035
TO
443 * Additional options. Possible values:
444 * - 'unique' (default: true) Check if this message was already set before adding
445 * - 'expires' how long to display this message before fadeout (in ms)
446 * set to 0 for no expiration
447 * defaults to 10 seconds for most messages, 5 if it has a title but no body,
448 * or 0 for errors or messages containing links
6a488035 449 */
be2fb01f 450 public static function setStatus($text, $title = '', $type = 'alert', $options = []) {
6a488035
TO
451 // make sure session is initialized, CRM-8120
452 $session = self::singleton();
453 $session->initialize();
454
98b2a189
SM
455 // Sanitize any HTML we're displaying. This helps prevent reflected XSS in error messages.
456 $text = CRM_Utils_String::purifyHTML($text);
457 $title = CRM_Utils_String::purifyHTML($title);
458
6a488035 459 // default options
be2fb01f 460 $options += ['unique' => TRUE];
6a488035
TO
461
462 if (!isset(self::$_singleton->_session[self::$_singleton->_key]['status'])) {
be2fb01f 463 self::$_singleton->_session[self::$_singleton->_key]['status'] = [];
6a488035 464 }
bc86ffa9 465 if ($text || $title) {
6a488035
TO
466 if ($options['unique']) {
467 foreach (self::$_singleton->_session[self::$_singleton->_key]['status'] as $msg) {
468 if ($msg['text'] == $text && $msg['title'] == $title) {
469 return;
470 }
471 }
472 }
473 unset($options['unique']);
be2fb01f 474 self::$_singleton->_session[self::$_singleton->_key]['status'][] = [
6a488035
TO
475 'text' => $text,
476 'title' => $title,
477 'type' => $type,
03a7ec8f 478 'options' => $options ? $options : NULL,
be2fb01f 479 ];
6a488035
TO
480 }
481 }
482
a0ee3941 483 /**
8eedd10a 484 * Register and retrieve session objects.
485 *
d4ad6ab3 486 * @param string|array $names
a0ee3941 487 */
00be9182 488 public static function registerAndRetrieveSessionObjects($names) {
6a488035 489 if (!is_array($names)) {
be2fb01f 490 $names = [$names];
6a488035
TO
491 }
492
493 if (!self::$_managedNames) {
494 self::$_managedNames = $names;
495 }
496 else {
497 self::$_managedNames = array_merge(self::$_managedNames, $names);
498 }
499
500 CRM_Core_BAO_Cache::restoreSessionFromCache($names);
501 }
502
a0ee3941 503 /**
8eedd10a 504 * Store session objects.
505 *
a0ee3941
EM
506 * @param bool $reset
507 */
00be9182 508 public static function storeSessionObjects($reset = TRUE) {
6a488035
TO
509 if (empty(self::$_managedNames)) {
510 return;
511 }
512
513 self::$_managedNames = CRM_Utils_Array::crmArrayUnique(self::$_managedNames);
514
515 CRM_Core_BAO_Cache::storeSessionToCache(self::$_managedNames, $reset);
516
517 self::$_managedNames = NULL;
518 }
519
bb341097 520 /**
f9e31d7f 521 * Retrieve contact id of the logged in user.
8eedd10a 522 *
e97c66ff 523 * @return int|null
72b3a70c 524 * contact ID of logged in user
bb341097 525 */
00be9182 526 public static function getLoggedInContactID() {
bb341097
EM
527 $session = CRM_Core_Session::singleton();
528 if (!is_numeric($session->get('userID'))) {
529 return NULL;
530 }
531 return $session->get('userID');
532 }
533
8f864776 534 /**
535 * Get display name of the logged in user.
536 *
537 * @return string
538 *
539 * @throws CiviCRM_API3_Exception
540 */
541 public function getLoggedInContactDisplayName() {
3bdcd4ec 542 $userContactID = CRM_Core_Session::getLoggedInContactID();
8f864776 543 if (!$userContactID) {
544 return '';
545 }
be2fb01f 546 return civicrm_api3('Contact', 'getvalue', ['id' => $userContactID, 'return' => 'display_name']);
8f864776 547 }
548
a0ee3941 549 /**
8eedd10a 550 * Check if session is empty.
551 *
552 * if so we don't cache stuff that we can get away with, helps proxies like varnish.
553 *
a0ee3941
EM
554 * @return bool
555 */
00be9182 556 public function isEmpty() {
d4ad6ab3 557 return empty($_SESSION);
6a488035 558 }
96025800 559
6a488035 560}