Completed
Branch BUG-11107-submit-button-text (879795)
by
unknown
11:38
created

EE_Session::update()   D

Complexity

Conditions 18
Paths 432

Size

Total Lines 70
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 48
nc 432
nop 1
dl 0
loc 70
rs 4.0413
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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