Completed
Branch FET/9575/invisible-recaptcha (d42449)
by
unknown
52:19 queued 39:45
created

EE_Session   F

Complexity

Total Complexity 148

Size/Duplication

Total Lines 1251
Duplicated Lines 2.56 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 32
loc 1251
rs 3.9999
c 0
b 0
f 0
wmc 148
lcom 1
cbo 11

42 Methods

Rating   Name   Duplication   Size   Complexity  
A instance() 0 19 3
B __construct() 0 47 6
A isLoadedAndActive() 0 6 3
A isActive() 0 4 1
A open_session() 0 8 2
A expired() 0 4 1
A reset_expired() 0 4 1
A expiration() 0 4 1
A extension() 0 4 1
A extend_expiration() 0 5 2
A lifespan() 0 4 1
A _set_defaults() 0 11 3
A id() 0 4 1
A set_cart() 0 5 1
A reset_cart() 0 5 1
A cart() 0 6 3
A set_checkout() 0 5 1
A reset_checkout() 0 5 1
A checkout() 0 6 3
A set_transaction() 0 7 1
A reset_transaction() 0 5 1
A transaction() 0 7 3
A get_session_data() 0 12 4
B set_session_data() 0 31 5
C _espresso_session() 0 49 8
F _retrieve_session_data() 10 93 17
A _generate_session_id() 0 10 2
A _get_sid_salt() 0 18 4
A _set_init_access_and_expiration() 0 9 1
D update() 0 70 18
A _create_espresso_session() 0 6 2
C _save_session_to_db() 0 35 8
B _get_page_visit() 0 29 6
A _wp_user_id() 0 6 1
A clear_session() 0 17 1
D reset_data() 13 43 9
A wp_loaded() 0 6 2
A reset_instance() 0 5 1
B configure_garbage_collection_filters() 0 24 3
B find_serialize_error() 9 57 7
A updateSessionSettings() 0 6 1
C garbageCollection() 0 44 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EE_Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EE_Session, and based on these observations, apply Extract Interface, too.

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