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