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