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