Completed
Branch FET/10339/exit-modal-for-ee-de... (81712e)
by
unknown
29:29 queued 13:19
created

EE_Session::cart()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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