Completed
Branch FET/11406/add-user-id-notices (2f10ee)
by
unknown
62:49 queued 50:36
created

EE_Session::isActive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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