Completed
Branch BUG-11049-session-transients (c25b27)
by
unknown
41:06 queued 30:34
created

EE_Session::updateSessionSettings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\domain\services\session\SessionIdentifierInterface;
4
use EventEspresso\core\exceptions\InvalidSessionDataException;
5
use EventEspresso\core\services\cache\CacheStorageInterface;
6
7
defined('EVENT_ESPRESSO_VERSION') || exit('NO direct script access allowed');
8
9
10
11
/**
12
 * EE_Session class
13
 *
14
 * @package    Event Espresso
15
 * @subpackage includes/classes
16
 * @author     Brent Christensen
17
 */
18
class EE_Session implements SessionIdentifierInterface
19
{
20
21
    const session_id_prefix    = 'ee_ssn_';
22
23
    const hash_check_prefix    = 'ee_shc_';
24
25
    const OPTION_NAME_SETTINGS = 'ee_session_settings';
26
27
    /**
28
     * instance of the EE_Session object
29
     *
30
     * @var EE_Session
31
     */
32
    private static $_instance;
33
34
    /**
35
     * @var CacheStorageInterface $cache_storage
36
     */
37
    protected $cache_storage;
38
39
    /**
40
     * EE_Encryption object
41
     *
42
     * @var EE_Encryption
43
     */
44
    protected $encryption;
45
46
    /**
47
     * the session id
48
     *
49
     * @var string
50
     */
51
    private $_sid;
52
53
    /**
54
     * session id salt
55
     *
56
     * @var string
57
     */
58
    private $_sid_salt;
59
60
    /**
61
     * session data
62
     *
63
     * @var array
64
     */
65
    private $_session_data = array();
66
67
    /**
68
     * how long an EE session lasts
69
     * default session lifespan of 2 hours (for not so instant IPNs)
70
     *
71
     * @var int
72
     */
73
    private $_lifespan;
74
75
    /**
76
     * session expiration time as Unix timestamp in GMT
77
     *
78
     * @var int
79
     */
80
    private $_expiration;
81
82
    /**
83
     * whether or not session has expired at some point
84
     *
85
     * @var boolean
86
     */
87
    private $_expired = false;
88
89
    /**
90
     * current time as Unix timestamp in GMT
91
     *
92
     * @var int
93
     */
94
    private $_time;
95
96
    /**
97
     * whether to encrypt session data
98
     *
99
     * @var bool
100
     */
101
    private $_use_encryption = false;
102
103
    /**
104
     * well... according to the server...
105
     *
106
     * @var null
107
     */
108
    private $_user_agent;
109
110
    /**
111
     * do you really trust the server ?
112
     *
113
     * @var null
114
     */
115
    private $_ip_address;
116
117
    /**
118
     * current WP user_id
119
     *
120
     * @var null
121
     */
122
    private $_wp_user_id;
123
124
    /**
125
     * array for defining default session vars
126
     *
127
     * @var array
128
     */
129
    private $_default_session_vars = array(
130
        'id'            => null,
131
        'user_id'       => null,
132
        'ip_address'    => null,
133
        'user_agent'    => null,
134
        'init_access'   => null,
135
        'last_access'   => null,
136
        'expiration'    => null,
137
        'pages_visited' => array(),
138
    );
139
140
    /**
141
     * timestamp for when last garbage collection cycle was performed
142
     *
143
     * @var int $_last_gc
144
     */
145
    private $_last_gc;
146
147
148
149
    /**
150
     * @singleton method used to instantiate class object
151
     * @param CacheStorageInterface $cache_storage
152
     * @param \EE_Encryption        $encryption
153
     * @return EE_Session
154
     * @throws InvalidSessionDataException
155
     * @throws \EE_Error
156
     */
157
    public static function instance(
158
        CacheStorageInterface $cache_storage = null,
159
        EE_Encryption $encryption = null
160
    ) {
161
        // check if class object is instantiated
162
        // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via:
163
        // add_filter( 'FHEE_load_EE_Session', '__return_false' );
164
        if (! self::$_instance instanceof EE_Session && apply_filters('FHEE_load_EE_Session', true)) {
165
            self::$_instance = new self($cache_storage, $encryption);
0 ignored issues
show
Bug introduced by
It seems like $cache_storage defined by parameter $cache_storage on line 158 can be null; however, EE_Session::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
166
        }
167
        return self::$_instance;
168
    }
169
170
171
172
    /**
173
     * protected constructor to prevent direct creation
174
     *
175
     * @param CacheStorageInterface $cache_storage
176
     * @param \EE_Encryption        $encryption
177
     * @throws \EE_Error
178
     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
179
     */
180
    protected function __construct(CacheStorageInterface $cache_storage, EE_Encryption $encryption = null)
181
    {
182
183
        // session loading is turned ON by default, but prior to the init hook, can be turned back OFF via: add_filter( 'FHEE_load_EE_Session', '__return_false' );
184
        if (! apply_filters('FHEE_load_EE_Session', true)) {
185
            return;
186
        }
187
        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
188
        if (! defined('ESPRESSO_SESSION')) {
189
            define('ESPRESSO_SESSION', true);
190
        }
191
        // default session lifespan in seconds
192
        $this->_lifespan = apply_filters(
193
                               'FHEE__EE_Session__construct___lifespan',
194
                               60 * MINUTE_IN_SECONDS
195
                           ) + 1;
196
        /*
197
         * do something like the following to adjust the session lifespan:
198
         * 		public static function session_lifespan() {
199
         * 			return 15 * MINUTE_IN_SECONDS;
200
         * 		}
201
         */
202
        // retrieve session options from db
203
        $session_settings = (array) get_option(EE_Session::OPTION_NAME_SETTINGS, array());
204
        if (! empty($session_settings)) {
205
            // cycle though existing session options
206
            foreach ($session_settings as $var_name => $session_setting) {
207
                // set values for class properties
208
                $var_name          = '_' . $var_name;
209
                $this->{$var_name} = $session_setting;
210
            }
211
        }
212
        $this->cache_storage = $cache_storage;
213
        // are we using encryption?
214
        $this->_use_encryption = $encryption instanceof EE_Encryption
215
                                 && EE_Registry::instance()->CFG->admin->encode_session_data();
216
        // \EEH_Debug_Tools::printr($this->_use_encryption, '$this->_use_encryption', __FILE__, __LINE__);
217
        // encrypt data via: $this->encryption->encrypt();
218
        $this->encryption = $encryption;
219
        // filter hook allows outside functions/classes/plugins to change default empty cart
220
        $extra_default_session_vars = apply_filters('FHEE__EE_Session__construct__extra_default_session_vars', array());
221
        array_merge($this->_default_session_vars, $extra_default_session_vars);
222
        // apply default session vars
223
        $this->_set_defaults();
224
        add_action('AHEE__EE_System__initialize', array($this, 'open_session'));
225
        // check request for 'clear_session' param
226
        add_action('AHEE__EE_Request_Handler__construct__complete', array($this, 'wp_loaded'));
227
        // once everything is all said and done,
228
        add_action('shutdown', array($this, 'update'), 100);
229
        add_action('shutdown', array($this, 'garbageCollection'), 1000);
230
        $this->configure_garbage_collection_filters();
231
    }
232
233
234
235
    /**
236
     * @return void
237
     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
238
     * @throws \EE_Error
239
     */
240
    public function open_session()
241
    {
242
        // check for existing session and retrieve it from db
243
        if (! $this->_espresso_session()) {
244
            // or just start a new one
245
            $this->_create_espresso_session();
246
        }
247
    }
248
249
250
251
    /**
252
     * @return bool
253
     */
254
    public function expired()
255
    {
256
        return $this->_expired;
257
    }
258
259
260
261
    /**
262
     * @return void
263
     */
264
    public function reset_expired()
265
    {
266
        $this->_expired = false;
267
    }
268
269
270
    /**
271
     * @return int
272
     */
273
    public function expiration()
274
    {
275
        return $this->_expiration;
276
    }
277
278
279
280
    /**
281
     * @return int
282
     */
283
    public function extension()
284
    {
285
        return apply_filters('FHEE__EE_Session__extend_expiration__seconds_added', (10 * MINUTE_IN_SECONDS));
286
    }
287
288
289
290
    /**
291
     * @param int $time number of seconds to add to session expiration
292
     */
293
    public function extend_expiration($time = 0)
294
    {
295
        $time              = $time ? $time : $this->extension();
296
        $this->_expiration += absint($time);
297
    }
298
299
300
301
    /**
302
     * @return int
303
     */
304
    public function lifespan()
305
    {
306
        return $this->_lifespan;
307
    }
308
309
310
311
    /**
312
     * This just sets some defaults for the _session data property
313
     *
314
     * @access private
315
     * @return void
316
     */
317
    private function _set_defaults()
318
    {
319
        // set some defaults
320
        foreach ($this->_default_session_vars as $key => $default_var) {
321
            if (is_array($default_var)) {
322
                $this->_session_data[ $key ] = array();
323
            } else {
324
                $this->_session_data[ $key ] = '';
325
            }
326
        }
327
    }
328
329
330
331
    /**
332
     * @retrieve  session data
333
     * @access    public
334
     * @return    string
335
     */
336
    public function id()
337
    {
338
        return $this->_sid;
339
    }
340
341
342
343
    /**
344
     * @param \EE_Cart $cart
345
     * @return bool
346
     */
347
    public function set_cart(EE_Cart $cart)
348
    {
349
        $this->_session_data['cart'] = $cart;
350
        return true;
351
    }
352
353
354
355
    /**
356
     * reset_cart
357
     */
358
    public function reset_cart()
359
    {
360
        do_action('AHEE__EE_Session__reset_cart__before_reset', $this);
361
        $this->_session_data['cart'] = null;
362
    }
363
364
365
366
    /**
367
     * @return \EE_Cart
368
     */
369
    public function cart()
370
    {
371
        return isset($this->_session_data['cart']) && $this->_session_data['cart'] instanceof EE_Cart
372
            ? $this->_session_data['cart']
373
            : null;
374
    }
375
376
377
378
    /**
379
     * @param \EE_Checkout $checkout
380
     * @return bool
381
     */
382
    public function set_checkout(EE_Checkout $checkout)
383
    {
384
        $this->_session_data['checkout'] = $checkout;
385
        return true;
386
    }
387
388
389
390
    /**
391
     * reset_checkout
392
     */
393
    public function reset_checkout()
394
    {
395
        do_action('AHEE__EE_Session__reset_checkout__before_reset', $this);
396
        $this->_session_data['checkout'] = null;
397
    }
398
399
400
401
    /**
402
     * @return \EE_Checkout
403
     */
404
    public function checkout()
405
    {
406
        return isset($this->_session_data['checkout']) && $this->_session_data['checkout'] instanceof EE_Checkout
407
            ? $this->_session_data['checkout']
408
            : null;
409
    }
410
411
412
413
    /**
414
     * @param \EE_Transaction $transaction
415
     * @return bool
416
     * @throws \EE_Error
417
     */
418
    public function set_transaction(EE_Transaction $transaction)
419
    {
420
        // first remove the session from the transaction before we save the transaction in the session
421
        $transaction->set_txn_session_data(null);
422
        $this->_session_data['transaction'] = $transaction;
423
        return true;
424
    }
425
426
427
428
    /**
429
     * reset_transaction
430
     */
431
    public function reset_transaction()
432
    {
433
        do_action('AHEE__EE_Session__reset_transaction__before_reset', $this);
434
        $this->_session_data['transaction'] = null;
435
    }
436
437
438
439
    /**
440
     * @return \EE_Transaction
441
     */
442
    public function transaction()
443
    {
444
        return isset($this->_session_data['transaction'])
445
               && $this->_session_data['transaction'] instanceof EE_Transaction
446
            ? $this->_session_data['transaction']
447
            : null;
448
    }
449
450
451
452
    /**
453
     * retrieve session data
454
     *
455
     * @access    public
456
     * @param null $key
457
     * @param bool $reset_cache
458
     * @return    array
459
     */
460
    public function get_session_data($key = null, $reset_cache = false)
461
    {
462
        if ($reset_cache) {
463
            $this->reset_cart();
464
            $this->reset_checkout();
465
            $this->reset_transaction();
466
        }
467
        if (! empty($key)) {
468
            return isset($this->_session_data[ $key ]) ? $this->_session_data[ $key ] : null;
469
        } else {
470
            return $this->_session_data;
471
        }
472
    }
473
474
475
476
    /**
477
     * set session data
478
     *
479
     * @access    public
480
     * @param    array $data
481
     * @return    TRUE on success, FALSE on fail
482
     */
483
    public function set_session_data($data)
484
    {
485
486
        // nothing ??? bad data ??? go home!
487
        if (empty($data) || ! is_array($data)) {
488
            EE_Error::add_error(__('No session data or invalid session data was provided.', 'event_espresso'), __FILE__,
489
                __FUNCTION__, __LINE__);
490
            return false;
491
        }
492
        foreach ($data as $key => $value) {
493
            if (isset($this->_default_session_vars[ $key ])) {
494
                EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
495
                    'event_espresso'), $key), __FILE__, __FUNCTION__, __LINE__);
496
                return false;
497
            } else {
498
                $this->_session_data[ $key ] = $value;
499
            }
500
        }
501
        return true;
502
    }
503
504
505
506
    /**
507
     * @initiate session
508
     * @access   private
509
     * @return TRUE on success, FALSE on fail
510
     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
511
     * @throws \EE_Error
512
     */
513
    private function _espresso_session()
514
    {
515
        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
516
        // check that session has started
517
        if (session_id() === '') {
518
            //starts a new session if one doesn't already exist, or re-initiates an existing one
519
            session_start();
520
        }
521
        // get our modified session ID
522
        $this->_sid = $this->_generate_session_id();
523
        // and the visitors IP
524
        $this->_ip_address = $this->_visitor_ip();
525
        // set the "user agent"
526
        $this->_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? esc_attr($_SERVER['HTTP_USER_AGENT']) : false;
527
        // now let's retrieve what's in the db
528
        $session_data = $this->_retrieve_session_data();
529
        if (! empty($session_data)) {
530
            // get the current time in UTC
531
            $this->_time = isset($this->_time) ? $this->_time : time();
532
            // and reset the session expiration
533
            $this->_expiration = isset($session_data['expiration'])
534
                ? $session_data['expiration']
535
                : $this->_time + $this->_lifespan;
536
        } else {
537
            // set initial site access time and the session expiration
538
            $this->_set_init_access_and_expiration();
539
            // set referer
540
            $this->_session_data['pages_visited'][ $this->_session_data['init_access'] ] = isset($_SERVER['HTTP_REFERER'])
541
                ? esc_attr($_SERVER['HTTP_REFERER'])
542
                : '';
543
            // no previous session = go back and create one (on top of the data above)
544
            return false;
545
        }
546
        // now the user agent
547
        if ($session_data['user_agent'] !== $this->_user_agent) {
548
            return false;
549
        }
550
        // wait a minute... how old are you?
551
        if ($this->_time > $this->_expiration) {
552
            // yer too old fer me!
553
            $this->_expired = true;
554
            // wipe out everything that isn't a default session datum
555
            $this->clear_session(__CLASS__, __FUNCTION__);
556
        }
557
        // make event espresso session data available to plugin
558
        $this->_session_data = array_merge($this->_session_data, $session_data);
559
        return true;
560
    }
561
562
563
564
    /**
565
     * _get_session_data
566
     * Retrieves the session data, and attempts to correct any encoding issues that can occur due to improperly setup
567
     * databases
568
     *
569
     * @return array
570
     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
571
     */
572
    protected function _retrieve_session_data()
573
    {
574
        $ssn_key = EE_Session::session_id_prefix . $this->_sid;
575
        try {
576
            // we're using WP's Transient API to store session data using the PHP session ID as the option name
577
            $session_data = $this->cache_storage->get($ssn_key, false);
578
            if (empty($session_data)) {
579
                return array();
580
            }
581
            if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
582
                $hash_check = $this->cache_storage->get(
583
                    EE_Session::hash_check_prefix . $this->_sid,
584
                    false
585
                );
586
                if ($hash_check && $hash_check !== md5($session_data)) {
587
                    EE_Error::add_error(
588
                        sprintf(
589
                            __(
590
                                'The stored data for session %1$s failed to pass a hash check and therefore appears to be invalid.',
591
                                'event_espresso'
592
                            ),
593
                            EE_Session::session_id_prefix . $this->_sid
594
                        ),
595
                        __FILE__, __FUNCTION__, __LINE__
596
                    );
597
                }
598
            }
599
        } catch (Exception $e) {
600
            // let's just eat that error for now and attempt to correct any corrupted data
601
            global $wpdb;
602
            $row          = $wpdb->get_row(
603
                $wpdb->prepare(
604
                    "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
605
                    '_transient_' . $ssn_key
606
                )
607
            );
608
            $session_data = is_object($row) ? $row->option_value : null;
609
            if ($session_data) {
610
                $session_data = preg_replace_callback(
611
                    '!s:(d+):"(.*?)";!',
612 View Code Duplication
                    function ($match)
613
                    {
614
                        return $match[1] === strlen($match[2])
615
                            ? $match[0]
616
                            : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
617
                    },
618
                    $session_data
619
                );
620
            }
621
            $session_data = maybe_unserialize($session_data);
622
        }
623
        // in case the data is encoded... try to decode it
624
        $session_data = $this->encryption instanceof EE_Encryption
625
            ? $this->encryption->base64_string_decode($session_data)
626
            : $session_data;
627
        if (! is_array($session_data)) {
628
            try {
629
                $session_data = maybe_unserialize($session_data);
630
            } catch (Exception $e) {
631
                $msg = esc_html__(
632
                    'An error occurred while attempting to unserialize the session data.',
633
                    'event_espresso'
634
                );
635
                $msg .= WP_DEBUG
636
                    ? '<br><pre>'
637
                      . print_r($session_data, true)
638
                      . '</pre><br>'
639
                      . $this->find_serialize_error($session_data)
640
                    : '';
641
                throw new InvalidSessionDataException($msg, 0, $e);
642
            }
643
        }
644
        // just a check to make sure the session array is indeed an array
645
        if (! is_array($session_data)) {
646
            // no?!?! then something is wrong
647
            $msg = esc_html__(
648
                'The session data is missing, invalid, or corrupted.',
649
                'event_espresso'
650
            );
651
            $msg .= WP_DEBUG
652
                ? '<br><pre>' . print_r($session_data, true) . '</pre><br>' . $this->find_serialize_error($session_data)
653
                : '';
654
            throw new InvalidSessionDataException($msg);
655
        }
656 View Code Duplication
        if (isset($session_data['transaction']) && absint($session_data['transaction']) !== 0) {
657
            $session_data['transaction'] = EEM_Transaction::instance()->get_one_by_ID(
658
                $session_data['transaction']
659
            );
660
        }
661
        return $session_data;
662
    }
663
664
665
666
    /**
667
     * _generate_session_id
668
     * Retrieves the PHP session id either directly from the PHP session,
669
     * or from the $_REQUEST array if it was passed in from an AJAX request.
670
     * The session id is then salted and hashed (mmm sounds tasty)
671
     * so that it can be safely used as a $_REQUEST param
672
     *
673
     * @return string
674
     */
675
    protected function _generate_session_id()
676
    {
677
        // check if the SID was passed explicitly, otherwise get from session, then add salt and hash it to reduce length
678
        if (isset($_REQUEST['EESID'])) {
679
            $session_id = sanitize_text_field($_REQUEST['EESID']);
680
        } else {
681
            $session_id = md5(session_id() . get_current_blog_id() . $this->_get_sid_salt());
682
        }
683
        return apply_filters('FHEE__EE_Session___generate_session_id__session_id', $session_id);
684
    }
685
686
687
688
    /**
689
     * _get_sid_salt
690
     *
691
     * @return string
692
     */
693
    protected function _get_sid_salt()
694
    {
695
        // was session id salt already saved to db ?
696
        if (empty($this->_sid_salt)) {
697
            // no?  then maybe use WP defined constant
698
            if (defined('AUTH_SALT')) {
699
                $this->_sid_salt = AUTH_SALT;
700
            }
701
            // if salt doesn't exist or is too short
702
            if (strlen($this->_sid_salt) < 32) {
703
                // create a new one
704
                $this->_sid_salt = wp_generate_password(64);
705
            }
706
            // and save it as a permanent session setting
707
            $this->updateSessionSettings(array('sid_salt' => $this->_sid_salt));
708
        }
709
        return $this->_sid_salt;
710
    }
711
712
713
714
    /**
715
     * _set_init_access_and_expiration
716
     *
717
     * @return void
718
     */
719
    protected function _set_init_access_and_expiration()
720
    {
721
        $this->_time       = time();
722
        $this->_expiration = $this->_time + $this->_lifespan;
723
        // set initial site access time
724
        $this->_session_data['init_access'] = $this->_time;
725
        // and the session expiration
726
        $this->_session_data['expiration'] = $this->_expiration;
727
    }
728
729
730
731
    /**
732
     * @update session data  prior to saving to the db
733
     * @access public
734
     * @param bool $new_session
735
     * @return TRUE on success, FALSE on fail
736
     * @throws \EE_Error
737
     */
738
    public function update($new_session = false)
739
    {
740
        $this->_session_data = isset($this->_session_data)
741
                               && is_array($this->_session_data)
742
                               && isset($this->_session_data['id'])
743
            ? $this->_session_data
744
            : array();
745
        if (empty($this->_session_data)) {
746
            $this->_set_defaults();
747
        }
748
        $session_data = array();
749
        foreach ($this->_session_data as $key => $value) {
750
751
            switch ($key) {
752
753
                case 'id' :
754
                    // session ID
755
                    $session_data['id'] = $this->_sid;
756
                    break;
757
                case 'ip_address' :
758
                    // visitor ip address
759
                    $session_data['ip_address'] = $this->_visitor_ip();
760
                    break;
761
                case 'user_agent' :
762
                    // visitor user_agent
763
                    $session_data['user_agent'] = $this->_user_agent;
764
                    break;
765
                case 'init_access' :
766
                    $session_data['init_access'] = absint($value);
767
                    break;
768
                case 'last_access' :
769
                    // current access time
770
                    $session_data['last_access'] = $this->_time;
771
                    break;
772
                case 'expiration' :
773
                    // when the session expires
774
                    $session_data['expiration'] = ! empty($this->_expiration)
775
                        ? $this->_expiration
776
                        : $session_data['init_access'] + $this->_lifespan;
777
                    break;
778
                case 'user_id' :
779
                    // current user if logged in
780
                    $session_data['user_id'] = $this->_wp_user_id();
781
                    break;
782
                case 'pages_visited' :
783
                    $page_visit = $this->_get_page_visit();
784
                    if ($page_visit) {
785
                        // set pages visited where the first will be the http referrer
786
                        $this->_session_data['pages_visited'][ $this->_time ] = $page_visit;
787
                        // we'll only save the last 10 page visits.
788
                        $session_data['pages_visited'] = array_slice($this->_session_data['pages_visited'], -10);
789
                    }
790
                    break;
791
                default :
792
                    // carry any other data over
793
                    $session_data[ $key ] = $this->_session_data[ $key ];
794
            }
795
        }
796
        $this->_session_data = $session_data;
797
        // creating a new session does not require saving to the db just yet
798
        if (! $new_session) {
799
            // ready? let's save
800
            if ($this->_save_session_to_db()) {
801
                return true;
802
            }
803
            return false;
804
        }
805
        // meh, why not?
806
        return true;
807
    }
808
809
810
811
    /**
812
     * @create session data array
813
     * @access public
814
     * @return bool
815
     * @throws \EE_Error
816
     */
817
    private function _create_espresso_session()
818
    {
819
        do_action('AHEE_log', __CLASS__, __FUNCTION__, '');
820
        // use the update function for now with $new_session arg set to TRUE
821
        return $this->update(true) ? true : false;
822
    }
823
824
825
826
    /**
827
     * _save_session_to_db
828
     *
829
     * @access public
830
     * @return string
831
     * @throws \EE_Error
832
     */
833
    private function _save_session_to_db()
834
    {
835
        if (
836
            // if the current request is NOT one of the following
837
        ! (
838
            // an an AJAX request from the frontend
839
            EE_Registry::instance()->REQ->front_ajax
840
            || (
841
                // OR an admin request that is NOT AJAX
842
                ! (defined('DOING_AJAX') && DOING_AJAX)
843
                && is_admin()
844
            )
845
            || (
846
                // OR an espresso page
847
                EE_Registry::instance()->REQ instanceof EE_Request_Handler
848
                && EE_Registry::instance()->REQ->is_espresso_page()
849
            )
850
        )
851
        ) {
852
            return false;
853
        }
854
        $transaction = $this->transaction();
855
        if ($transaction instanceof EE_Transaction) {
856
            if (! $transaction->ID()) {
857
                $transaction->save();
858
            }
859
            $this->_session_data['transaction'] = $transaction->ID();
860
        }
861
        // then serialize all of our session data
862
        $session_data = serialize($this->_session_data);
863
        // do we need to also encode it to avoid corrupted data when saved to the db?
864
        $session_data = $this->_use_encryption
865
            ? $this->encryption->base64_string_encode($session_data)
866
            : $session_data;
867
        // maybe save hash check
868
        if (apply_filters('FHEE__EE_Session___perform_session_id_hash_check', WP_DEBUG)) {
869
            $this->cache_storage->add(
870
                EE_Session::hash_check_prefix . $this->_sid,
871
                md5($session_data),
872
                $this->_lifespan
873
            );
874
        }
875
        // we're using the Transient API for storing session data,
876
        return $this->cache_storage->add(
877
            EE_Session::session_id_prefix . $this->_sid,
878
            $session_data,
879
            $this->_lifespan
880
        );
881
    }
882
883
884
885
    /**
886
     * _visitor_ip
887
     *    attempt to get IP address of current visitor from server
888
     * plz see: http://stackoverflow.com/a/2031935/1475279
889
     *
890
     * @access public
891
     * @return string
892
     */
893 View Code Duplication
    private function _visitor_ip()
894
    {
895
        $visitor_ip  = '0.0.0.0';
896
        $server_keys = array(
897
            'HTTP_CLIENT_IP',
898
            'HTTP_X_FORWARDED_FOR',
899
            'HTTP_X_FORWARDED',
900
            'HTTP_X_CLUSTER_CLIENT_IP',
901
            'HTTP_FORWARDED_FOR',
902
            'HTTP_FORWARDED',
903
            'REMOTE_ADDR',
904
        );
905
        foreach ($server_keys as $key) {
906
            if (isset($_SERVER[ $key ])) {
907
                foreach (array_map('trim', explode(',', $_SERVER[ $key ])) as $ip) {
908
                    if ($ip === '127.0.0.1' || filter_var($ip, FILTER_VALIDATE_IP) !== false) {
909
                        $visitor_ip = $ip;
910
                    }
911
                }
912
            }
913
        }
914
        return $visitor_ip;
915
    }
916
917
918
919
    /**
920
     * @get    the full page request the visitor is accessing
921
     * @access public
922
     * @return string
923
     */
924
    public function _get_page_visit()
925
    {
926
        $page_visit = home_url('/') . 'wp-admin/admin-ajax.php';
927
        // check for request url
928
        if (isset($_SERVER['REQUEST_URI'])) {
929
            $http_host   = '';
930
            $page_id     = '?';
931
            $e_reg       = '';
932
            $request_uri = esc_url($_SERVER['REQUEST_URI']);
933
            $ru_bits     = explode('?', $request_uri);
934
            $request_uri = $ru_bits[0];
935
            // check for and grab host as well
936
            if (isset($_SERVER['HTTP_HOST'])) {
937
                $http_host = esc_url($_SERVER['HTTP_HOST']);
938
            }
939
            // check for page_id in SERVER REQUEST
940
            if (isset($_REQUEST['page_id'])) {
941
                // rebuild $e_reg without any of the extra parameters
942
                $page_id = '?page_id=' . esc_attr($_REQUEST['page_id']) . '&amp;';
943
            }
944
            // check for $e_reg in SERVER REQUEST
945
            if (isset($_REQUEST['ee'])) {
946
                // rebuild $e_reg without any of the extra parameters
947
                $e_reg = 'ee=' . esc_attr($_REQUEST['ee']);
948
            }
949
            $page_visit = rtrim($http_host . $request_uri . $page_id . $e_reg, '?');
950
        }
951
        return $page_visit !== home_url('/wp-admin/admin-ajax.php') ? $page_visit : '';
952
    }
953
954
955
956
    /**
957
     * @the    current wp user id
958
     * @access public
959
     * @return int
960
     */
961
    public function _wp_user_id()
962
    {
963
        // if I need to explain the following lines of code, then you shouldn't be looking at this!
964
        $this->_wp_user_id = get_current_user_id();
965
        return $this->_wp_user_id;
966
    }
967
968
969
970
    /**
971
     * Clear EE_Session data
972
     *
973
     * @access public
974
     * @param string $class
975
     * @param string $function
976
     * @return void
977
     * @throws \EE_Error
978
     */
979
    public function clear_session($class = '', $function = '')
980
    {
981
        //echo '<h3 style="color:#999;line-height:.9em;"><span style="color:#2EA2CC">' . __CLASS__ . '</span>::<span style="color:#E76700">' . __FUNCTION__ . '( ' . $class . '::' . $function . '() )</span><br/><span style="font-size:9px;font-weight:normal;">' . __FILE__ . '</span>    <b style="font-size:10px;">  ' . __LINE__ . ' </b></h3>';
982
        do_action('AHEE_log', __FILE__, __FUNCTION__, 'session cleared by : ' . $class . '::' . $function . '()');
983
        $this->reset_cart();
984
        $this->reset_checkout();
985
        $this->reset_transaction();
986
        // wipe out everything that isn't a default session datum
987
        $this->reset_data(array_keys($this->_session_data));
988
        // reset initial site access time and the session expiration
989
        $this->_set_init_access_and_expiration();
990
        $this->_save_session_to_db();
991
    }
992
993
994
995
    /**
996
     * @resets all non-default session vars
997
     * @access public
998
     * @param array $data_to_reset
999
     * @param bool  $show_all_notices
1000
     * @return TRUE on success, FALSE on fail
1001
     */
1002
    public function reset_data($data_to_reset = array(), $show_all_notices = false)
1003
    {
1004
        // if $data_to_reset is not in an array, then put it in one
1005
        if (! is_array($data_to_reset)) {
1006
            $data_to_reset = array($data_to_reset);
1007
        }
1008
        // nothing ??? go home!
1009
        if (empty($data_to_reset)) {
1010
            EE_Error::add_error(__('No session data could be reset, because no session var name was provided.',
1011
                'event_espresso'), __FILE__, __FUNCTION__, __LINE__);
1012
            return false;
1013
        }
1014
        $return_value = true;
1015
        // since $data_to_reset is an array, cycle through the values
1016
        foreach ($data_to_reset as $reset) {
1017
1018
            // first check to make sure it is a valid session var
1019
            if (isset($this->_session_data[ $reset ])) {
1020
                // then check to make sure it is not a default var
1021
                if (! array_key_exists($reset, $this->_default_session_vars)) {
1022
                    // remove session var
1023
                    unset($this->_session_data[ $reset ]);
1024
                    if ($show_all_notices) {
1025
                        EE_Error::add_success(sprintf(__('The session variable %s was removed.', 'event_espresso'),
1026
                            $reset), __FILE__, __FUNCTION__, __LINE__);
1027
                    }
1028
                    $return_value = ! isset($return_value) ? true : $return_value;
1029 View Code Duplication
                } else {
1030
                    // yeeeeeeeeerrrrrrrrrrr OUT !!!!
1031
                    if ($show_all_notices) {
1032
                        EE_Error::add_error(sprintf(__('Sorry! %s is a default session datum and can not be reset.',
1033
                            'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1034
                    }
1035
                    $return_value = false;
1036
                }
1037 View Code Duplication
            } elseif ($show_all_notices) {
1038
                // oops! that session var does not exist!
1039
                EE_Error::add_error(sprintf(__('The session item provided, %s, is invalid or does not exist.',
1040
                    'event_espresso'), $reset), __FILE__, __FUNCTION__, __LINE__);
1041
                $return_value = false;
1042
            }
1043
        } // end of foreach
1044
        return $return_value;
1045
    }
1046
1047
1048
1049
    /**
1050
     *   wp_loaded
1051
     *
1052
     * @access public
1053
     * @throws \EE_Error
1054
     */
1055
    public function wp_loaded()
1056
    {
1057
        if (isset(EE_Registry::instance()->REQ) && EE_Registry::instance()->REQ->is_set('clear_session')) {
1058
            $this->clear_session(__CLASS__, __FUNCTION__);
1059
        }
1060
    }
1061
1062
1063
1064
    /**
1065
     * Used to reset the entire object (for tests).
1066
     *
1067
     * @since 4.3.0
1068
     * @throws \EE_Error
1069
     */
1070
    public function reset_instance()
1071
    {
1072
        $this->clear_session();
1073
        self::$_instance = null;
1074
    }
1075
1076
1077
1078
    public function configure_garbage_collection_filters()
1079
    {
1080
        // run old filter we had for controlling session cleanup
1081
        $expired_session_transient_delete_query_limit = absint(
1082
            apply_filters(
1083
                'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1084
                50
1085
            )
1086
        );
1087
        // is there a value? or one that is different than the default 50 records?
1088
        if ($expired_session_transient_delete_query_limit === 0) {
1089
            // hook into TransientCacheStorage in case Session cleanup was turned off
1090
            add_filter('FHEE__TransientCacheStorage__transient_cleanup_schedule', '__return_zero');
1091
        } elseif ($expired_session_transient_delete_query_limit !== 50) {
1092
            // or use that for the new transient cleanup query limit
1093
            add_filter(
1094
                'FHEE__TransientCacheStorage__clearExpiredTransients__limit',
1095
                function () use ($expired_session_transient_delete_query_limit)
1096
                {
1097
                    return $expired_session_transient_delete_query_limit;
1098
                }
1099
            );
1100
        }
1101
    }
1102
1103
1104
1105
    /**
1106
     * @see http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset/21389439#10152996
1107
     * @param $data1
1108
     * @return string
1109
     */
1110
    private function find_serialize_error($data1)
1111
    {
1112
        $error = '<pre>';
1113
        $data2 = preg_replace_callback(
1114
            '!s:(\d+):"(.*?)";!',
1115 View Code Duplication
            function ($match)
1116
            {
1117
                return ($match[1] === strlen($match[2]))
1118
                    ? $match[0]
1119
                    : 's:'
1120
                      . strlen($match[2])
1121
                      . ':"'
1122
                      . $match[2]
1123
                      . '";';
1124
            },
1125
            $data1
1126
        );
1127
        $max   = (strlen($data1) > strlen($data2)) ? strlen($data1) : strlen($data2);
1128
        $error .= $data1 . PHP_EOL;
1129
        $error .= $data2 . PHP_EOL;
1130
        for ($i = 0; $i < $max; $i++) {
1131
            if (@$data1[ $i ] !== @$data2[ $i ]) {
1132
                $error  .= 'Difference ' . @$data1[ $i ] . ' != ' . @$data2[ $i ] . PHP_EOL;
1133
                $error  .= "\t-> ORD number " . ord(@$data1[ $i ]) . ' != ' . ord(@$data2[ $i ]) . PHP_EOL;
1134
                $error  .= "\t-> Line Number = $i" . PHP_EOL;
1135
                $start  = ($i - 20);
1136
                $start  = ($start < 0) ? 0 : $start;
1137
                $length = 40;
1138
                $point  = $max - $i;
1139
                if ($point < 20) {
1140
                    $rlength = 1;
1141
                    $rpoint  = -$point;
1142
                } else {
1143
                    $rpoint  = $length - 20;
1144
                    $rlength = 1;
1145
                }
1146
                $error .= "\t-> Section Data1  = ";
1147
                $error .= substr_replace(
1148
                    substr($data1, $start, $length),
1149
                    "<b style=\"color:green\">{$data1[ $i ]}</b>",
1150
                    $rpoint,
1151
                    $rlength
1152
                );
1153
                $error .= PHP_EOL;
1154
                $error .= "\t-> Section Data2  = ";
1155
                $error .= substr_replace(
1156
                    substr($data2, $start, $length),
1157
                    "<b style=\"color:red\">{$data2[ $i ]}</b>",
1158
                    $rpoint,
1159
                    $rlength
1160
                );
1161
                $error .= PHP_EOL;
1162
            }
1163
        }
1164
        $error .= '</pre>';
1165
        return $error;
1166
    }
1167
1168
1169
    /**
1170
     * Saves an  array of settings used for configuring aspects of session behaviour
1171
     *
1172
     * @param array $updated_settings
1173
     */
1174
    private function updateSessionSettings(array $updated_settings = array())
1175
    {
1176
        // add existing settings, but only if not included in incoming $updated_settings array
1177
        $updated_settings += get_option(EE_Session::OPTION_NAME_SETTINGS, array());
1178
        update_option(EE_Session::OPTION_NAME_SETTINGS, $updated_settings);
1179
    }
1180
1181
1182
    /**
1183
     * garbage_collection
1184
     */
1185
    public function garbageCollection()
1186
    {
1187
        // only perform during regular requests if last garbage collection was over an hour ago
1188
        if (! (defined('DOING_AJAX') && DOING_AJAX) && (time() - HOUR_IN_SECONDS) >= $this->_last_gc) {
1189
            $this->_last_gc = time();
1190
            $this->updateSessionSettings(array('last_gc' => $this->_last_gc));
1191
            /** @type WPDB $wpdb */
1192
            global $wpdb;
1193
            // filter the query limit. Set to 0 to turn off garbage collection
1194
            $expired_session_transient_delete_query_limit = absint(
1195
                apply_filters(
1196
                    'FHEE__EE_Session__garbage_collection___expired_session_transient_delete_query_limit',
1197
                    50
1198
                )
1199
            );
1200
            // non-zero LIMIT means take out the trash
1201
            if ($expired_session_transient_delete_query_limit) {
1202
                $session_key    = str_replace('_', '\_', EE_Session::session_id_prefix);
1203
                $hash_check_key = str_replace('_', '\_', EE_Session::hash_check_prefix);
1204
                // since transient expiration timestamps are set in the future, we can compare against NOW
1205
                // but we only want to pick up any trash that's been around for more than a day
1206
                $expiration = time() - DAY_IN_SECONDS;
1207
                $SQL        = "
1208
                    SELECT option_name
1209
                    FROM {$wpdb->options}
1210
                    WHERE
1211
                      ( option_name LIKE '\_transient\_timeout\_{$session_key}%'
1212
                      OR option_name LIKE '\_transient\_timeout\_{$hash_check_key}%' )
1213
                    AND option_value < {$expiration}
1214
                    LIMIT {$expired_session_transient_delete_query_limit}
1215
                ";
1216
                // produces something like:
1217
                // SELECT option_name FROM wp_options
1218
                // WHERE ( option_name LIKE '\_transient\_timeout\_ee\_ssn\_%'
1219
                // OR option_name LIKE '\_transient\_timeout\_ee\_shc\_%' )
1220
                // AND option_value < 1508368198 LIMIT 50
1221
                $expired_sessions = $wpdb->get_col($SQL);
1222
                // valid results?
1223
                if (! $expired_sessions instanceof WP_Error && ! empty($expired_sessions)) {
1224
                    $this->cache_storage->deleteMany($expired_sessions, true);
1225
                }
1226
            }
1227
        }
1228
    }
1229
1230
1231
1232
}
1233
/* End of file EE_Session.class.php */
1234
/* Location: /includes/classes/EE_Session.class.php */
1235