Completed
Branch FET/11406/add-user-id-notices (d4cae6)
by
unknown
54:36 queued 36:02
created

EE_Session::reset_data()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 43
Code Lines 25

Duplication

Lines 13
Ratio 30.23 %

Importance

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