WPInv_Session_Handler::generate_customer_id()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Handle data for the current customers session.
4
 * Implements the WPInv_Session abstract class.
5
 *
6
 */
7
8
defined( 'ABSPATH' ) || exit;
9
10
/**
11
 * Session handler class.
12
 *
13
 * @deprecated
14
 */
15
class WPInv_Session_Handler extends WPInv_Session {
16
17
	/**
18
	 * Cookie name used for the session.
19
	 *
20
	 * @var string cookie name
21
	 */
22
	protected $_cookie;
23
24
	/**
25
	 * Stores session expiry.
26
	 *
27
	 * @var int session due to expire timestamp
28
	 */
29
	protected $_session_expiring;
30
31
	/**
32
	 * Stores session due to expire timestamp.
33
	 *
34
	 * @var string session expiration timestamp
35
	 */
36
	protected $_session_expiration;
37
38
	/**
39
	 * True when the cookie exists.
40
	 *
41
	 * @var bool Based on whether a cookie exists.
42
	 */
43
	protected $_has_cookie = false;
44
45
	/**
46
	 * Table name for session data.
47
	 *
48
	 * @var string Custom session table name
49
	 */
50
	protected $_table;
51
52
	/**
53
	 * Constructor for the session class.
54
	 */
55
	public function old__construct() {
56
57
	    $this->_cookie = apply_filters( 'wpinv_cookie', 'wpinv_session_' . COOKIEHASH );
58
        add_action( 'init', array( $this, 'init' ), -1 );
59
		add_action( 'wp_logout', array( $this, 'destroy_session' ) );
60
		add_action( 'wp', array( $this, 'set_customer_session_cookie' ), 10 );
61
		add_action( 'shutdown', array( $this, 'save_data' ), 20 );
62
63
	}
64
65
	/**
66
	 * Init hooks and session data.
67
	 *
68
	 * @since 3.3.0
69
	 */
70
	public function init() {
71
		$this->init_session_cookie();
72
73
		if ( ! is_user_logged_in() ) {
74
			add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ), 10, 2 );
75
		}
76
	}
77
78
	/**
79
	 * Setup cookie and customer ID.
80
	 *
81
	 * @since 3.6.0
82
	 */
83
	public function init_session_cookie() {
84
		$cookie = $this->get_session_cookie();
85
86
		if ( $cookie ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cookie of type array<integer,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$cookie is a non-empty array, thus is always true.
Loading history...
87
			$this->_customer_id        = $cookie[0];
0 ignored issues
show
Documentation Bug introduced by
The property $_customer_id was declared of type integer, but $cookie[0] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
88
			$this->_session_expiration = $cookie[1];
89
			$this->_session_expiring   = $cookie[2];
0 ignored issues
show
Documentation Bug introduced by
The property $_session_expiring was declared of type integer, but $cookie[2] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
90
			$this->_has_cookie         = true;
91
			$this->_data               = $this->get_session_data();
92
93
			// If the user logs in, update session.
94
			if ( is_user_logged_in() && get_current_user_id() != $this->_customer_id ) {
95
				$this->_customer_id = get_current_user_id();
96
				$this->_dirty       = true;
97
				$this->save_data();
98
				$this->set_customer_session_cookie( true );
99
			}
100
101
			// Update session if its close to expiring.
102
			if ( time() > $this->_session_expiring ) {
103
				$this->set_session_expiration();
104
				$this->update_session_timestamp( $this->_customer_id, $this->_session_expiration );
105
			}
106
		} else {
107
			$this->set_session_expiration();
108
			$this->_customer_id = $this->generate_customer_id();
109
			$this->_data        = $this->get_session_data();
110
		}
111
	}
112
113
	/**
114
	 * Sets the session cookie on-demand (usually after adding an item to the cart).
115
	 *
116
	 * Since the cookie name (as of 2.1) is prepended with wp, cache systems like batcache will not cache pages when set.
117
	 *
118
	 * Warning: Cookies will only be set if this is called before the headers are sent.
119
	 *
120
	 * @param bool $set Should the session cookie be set.
121
	 */
122
	public function set_customer_session_cookie( $set ) {
123
		if ( $set ) {
124
			$to_hash           = $this->_customer_id . '|' . $this->_session_expiration;
125
			$cookie_hash       = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
126
			$cookie_value      = $this->_customer_id . '||' . $this->_session_expiration . '||' . $this->_session_expiring . '||' . $cookie_hash;
127
			$this->_has_cookie = true;
128
129
			if ( ! isset( $_COOKIE[ $this->_cookie ] ) || $_COOKIE[ $this->_cookie ] !== $cookie_value ) {
130
				$this->setcookie( $this->_cookie, $cookie_value, $this->_session_expiration, $this->use_secure_cookie(), true );
131
			}
132
		}
133
	}
134
135
	public function setcookie( $name, $value, $expire = 0, $secure = false, $httponly = false ) {
136
        if ( ! headers_sent() ) {
137
            setcookie( $name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, $secure, apply_filters( 'wpinv_cookie_httponly', $httponly, $name, $value, $expire, $secure ) );
0 ignored issues
show
Bug introduced by
COOKIE_DOMAIN of type false is incompatible with the type string expected by parameter $domain of setcookie(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

137
            setcookie( $name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', /** @scrutinizer ignore-type */ COOKIE_DOMAIN, $secure, apply_filters( 'wpinv_cookie_httponly', $httponly, $name, $value, $expire, $secure ) );
Loading history...
138
        } elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
139
            headers_sent( $file, $line );
140
            trigger_error( "{$name} cookie cannot be set - headers already sent by {$file} on line {$line}", E_USER_NOTICE ); // @codingStandardsIgnoreLine
141
        }
142
    }
143
144
	/**
145
	 * Should the session cookie be secure?
146
	 *
147
	 * @since 3.6.0
148
	 * @return bool
149
	 */
150
	protected function use_secure_cookie() {
151
        $is_https = false !== strstr( get_option( 'home' ), 'https:' );
0 ignored issues
show
Bug introduced by
It seems like get_option('home') can also be of type false; however, parameter $haystack of strstr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
        $is_https = false !== strstr( /** @scrutinizer ignore-type */ get_option( 'home' ), 'https:' );
Loading history...
152
		return apply_filters( 'wpinv_session_use_secure_cookie', $is_https && is_ssl() );
153
	}
154
155
	/**
156
	 * Return true if the current user has an active session, i.e. a cookie to retrieve values.
157
	 *
158
	 * @return bool
159
	 */
160
	public function has_session() {
161
		return isset( $_COOKIE[ $this->_cookie ] ) || $this->_has_cookie || is_user_logged_in(); // @codingStandardsIgnoreLine.
162
	}
163
164
	/**
165
	 * Set session expiration.
166
	 */
167
	public function set_session_expiration() {
168
		$this->_session_expiring   = time() + intval( apply_filters( 'wpinv_session_expiring', 60 * 60 * 47 ) ); // 47 Hours.
169
		$this->_session_expiration = time() + intval( apply_filters( 'wpinv_session_expiration', 60 * 60 * 48 ) ); // 48 Hours.
170
	}
171
172
	/**
173
	 * Generates session ids.
174
	 *
175
	 * @return string
176
	 */
177
	public function generate_customer_id() {
178
		require_once ABSPATH . 'wp-includes/class-phpass.php';
179
		$hasher      = new PasswordHash( 8, false );
180
		return md5( $hasher->get_random_bytes( 32 ) );
181
	}
182
183
	/**
184
	 * Get the session cookie, if set. Otherwise return false.
185
	 *
186
	 * Session cookies without a customer ID are invalid.
187
	 *
188
	 * @return bool|array
189
	 */
190
	public function get_session_cookie() {
191
		$cookie_value = isset( $_COOKIE[ $this->_cookie ] ) ? wp_unslash( $_COOKIE[ $this->_cookie ] ) : false; // @codingStandardsIgnoreLine.
192
193
		if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) {
194
			return false;
195
		}
196
197
		list( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $cookie_value );
198
199
		if ( empty( $customer_id ) ) {
200
			return false;
201
		}
202
203
		// Validate hash.
204
		$to_hash = $customer_id . '|' . $session_expiration;
205
		$hash    = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
206
207
		if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) {
208
			return false;
209
		}
210
211
		return array( $customer_id, $session_expiration, $session_expiring, $cookie_hash );
212
	}
213
214
	/**
215
	 * Get session data.
216
	 *
217
	 * @return array
218
	 */
219
	public function get_session_data() {
220
		return $this->has_session() ? (array) $this->get_session( $this->_customer_id ) : array();
221
	}
222
223
	public function generate_key( $customer_id ) {
224
        if ( ! $customer_id ) {
225
            return;
226
        }
227
228
        return 'wpi_trans_' . $customer_id;
229
    }
230
231
	/**
232
	 * Save data.
233
	 */
234
	public function save_data() {
235
		// Dirty if something changed - prevents saving nothing new.
236
		if ( $this->_dirty && $this->has_session() ) {
237
238
            set_transient( $this->generate_key( $this->_customer_id ), $this->_data, $this->_session_expiration );
0 ignored issues
show
Bug introduced by
$this->_session_expiration of type string is incompatible with the type integer expected by parameter $expiration of set_transient(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

238
            set_transient( $this->generate_key( $this->_customer_id ), $this->_data, /** @scrutinizer ignore-type */ $this->_session_expiration );
Loading history...
239
240
			$this->_dirty = false;
241
		}
242
	}
243
244
	/**
245
	 * Destroy all session data.
246
	 */
247
	public function destroy_session() {
248
		$this->delete_session( $this->_customer_id );
249
		$this->forget_session();
250
	}
251
252
	/**
253
	 * Forget all session data without destroying it.
254
	 */
255
	public function forget_session() {
256
		$this->setcookie( $this->_cookie, '', time() - YEAR_IN_SECONDS, $this->use_secure_cookie(), true );
257
258
		wpinv_empty_cart();
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_empty_cart() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

258
		/** @scrutinizer ignore-deprecated */ wpinv_empty_cart();
Loading history...
259
260
		$this->_data        = array();
261
		$this->_dirty       = false;
262
		$this->_customer_id = $this->generate_customer_id();
0 ignored issues
show
Documentation Bug introduced by
The property $_customer_id was declared of type integer, but $this->generate_customer_id() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
263
	}
264
265
	/**
266
	 * When a user is logged out, ensure they have a unique nonce by using the customer/session ID.
267
	 *
268
	 * @param int $uid User ID.
269
	 * @return string
270
	 */
271
	public function nonce_user_logged_out( $uid ) {
272
273
		// Check if one of our nonces.
274
		if ( substr( $uid, 0, 5 ) === 'wpinv' || substr( $uid, 0, 7 ) === 'getpaid' ) {
275
			return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid;
276
		}
277
278
		return $uid;
279
	}
280
281
	/**
282
	 * Returns the session.
283
	 *
284
	 * @param string $customer_id Customer ID.
285
	 * @param mixed  $default Default session value.
286
	 * @return string|array
287
	 */
288
	public function get_session( $customer_id, $default = false ) {
289
290
		if ( defined( 'WP_SETUP_CONFIG' ) ) {
291
			return array();
292
		}
293
294
        $key = $this->generate_key( $customer_id );
295
        $value = get_transient( $key );
296
297
        if ( ! $value ) {
298
            $value = $default;
299
        }
300
301
		return maybe_unserialize( $value );
302
	}
303
304
	/**
305
	 * Delete the session from the cache and database.
306
	 *
307
	 * @param int $customer_id Customer ID.
308
	 */
309
	public function delete_session( $customer_id ) {
310
311
        $key = $this->generate_key( $customer_id );
312
313
		delete_transient( $key );
314
	}
315
316
	/**
317
	 * Update the session expiry timestamp.
318
	 *
319
	 * @param string $customer_id Customer ID.
320
	 * @param int    $timestamp Timestamp to expire the cookie.
321
	 */
322
	public function update_session_timestamp( $customer_id, $timestamp ) {
323
324
        set_transient( $this->generate_key( $customer_id ), maybe_serialize( $this->_data ), $timestamp );
325
326
	}
327
}
328