Completed
Branch FET/11174/bot-detection-middle... (17f260)
by
unknown
93:18 queued 82:13
created

EE_Session::reset_data()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 43
Code Lines 25

Duplication

Lines 13
Ratio 30.23 %

Importance

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