1
|
|
|
<?php |
2
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
3
|
|
|
exit; |
4
|
|
|
} |
5
|
|
|
|
6
|
|
|
if ( ! class_exists( 'WC_Session' ) ) { |
7
|
|
|
include_once( 'abstracts/abstract-wc-session.php' ); |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Handle data for the current customers session. |
12
|
|
|
* Implements the WC_Session abstract class. |
13
|
|
|
* |
14
|
|
|
* From 2.5 this uses a custom table for session storage. Based on https://github.com/kloon/woocommerce-large-sessions. |
15
|
|
|
* |
16
|
|
|
* @class WC_Session_Handler |
17
|
|
|
* @version 2.5.0 |
18
|
|
|
* @package WooCommerce/Classes |
19
|
|
|
* @category Class |
20
|
|
|
* @author WooThemes |
21
|
|
|
*/ |
22
|
|
|
class WC_Session_Handler extends WC_Session { |
23
|
|
|
|
24
|
|
|
/** @var string cookie name */ |
25
|
|
|
private $_cookie; |
26
|
|
|
|
27
|
|
|
/** @var string session due to expire timestamp */ |
28
|
|
|
private $_session_expiring; |
29
|
|
|
|
30
|
|
|
/** @var string session expiration timestamp */ |
31
|
|
|
private $_session_expiration; |
32
|
|
|
|
33
|
|
|
/** $var bool Bool based on whether a cookie exists **/ |
34
|
|
|
private $_has_cookie = false; |
35
|
|
|
|
36
|
|
|
/** @var string Custom session table name */ |
37
|
|
|
private $_table; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Constructor for the session class. |
41
|
|
|
*/ |
42
|
|
|
public function __construct() { |
43
|
|
|
global $wpdb; |
44
|
|
|
|
45
|
|
|
$this->_cookie = 'wp_woocommerce_session_' . COOKIEHASH; |
46
|
|
|
$this->_table = $wpdb->prefix . 'woocommerce_sessions'; |
47
|
|
|
|
48
|
|
|
if ( $cookie = $this->get_session_cookie() ) { |
49
|
|
|
$this->_customer_id = $cookie[0]; |
50
|
|
|
$this->_session_expiration = $cookie[1]; |
51
|
|
|
$this->_session_expiring = $cookie[2]; |
52
|
|
|
$this->_has_cookie = true; |
53
|
|
|
|
54
|
|
|
// Update session if its close to expiring |
55
|
|
|
if ( time() > $this->_session_expiring ) { |
56
|
|
|
$this->set_session_expiration(); |
57
|
|
|
$this->update_session_timestamp( $this->_customer_id, $this->_session_expiration ); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
} else { |
61
|
|
|
$this->set_session_expiration(); |
62
|
|
|
$this->_customer_id = $this->generate_customer_id(); |
|
|
|
|
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$this->_data = $this->get_session_data(); |
66
|
|
|
|
67
|
|
|
// Actions |
68
|
|
|
add_action( 'woocommerce_set_cart_cookies', array( $this, 'set_customer_session_cookie' ), 10 ); |
69
|
|
|
add_action( 'woocommerce_cleanup_sessions', array( $this, 'cleanup_sessions' ), 10 ); |
70
|
|
|
add_action( 'shutdown', array( $this, 'save_data' ), 20 ); |
71
|
|
|
add_action( 'wp_logout', array( $this, 'destroy_session' ) ); |
72
|
|
|
if ( ! is_user_logged_in() ) { |
73
|
|
|
add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ) ); |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Sets the session cookie on-demand (usually after adding an item to the cart). |
79
|
|
|
* |
80
|
|
|
* Since the cookie name (as of 2.1) is prepended with wp, cache systems like batcache will not cache pages when set. |
81
|
|
|
* |
82
|
|
|
* Warning: Cookies will only be set if this is called before the headers are sent. |
83
|
|
|
*/ |
84
|
|
|
public function set_customer_session_cookie( $set ) { |
85
|
|
|
if ( $set ) { |
86
|
|
|
// Set/renew our cookie |
87
|
|
|
$to_hash = $this->_customer_id . '|' . $this->_session_expiration; |
88
|
|
|
$cookie_hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) ); |
89
|
|
|
$cookie_value = $this->_customer_id . '||' . $this->_session_expiration . '||' . $this->_session_expiring . '||' . $cookie_hash; |
90
|
|
|
$this->_has_cookie = true; |
91
|
|
|
|
92
|
|
|
// Set the cookie |
93
|
|
|
wc_setcookie( $this->_cookie, $cookie_value, $this->_session_expiration, apply_filters( 'wc_session_use_secure_cookie', false ) ); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Return true if the current user has an active session, i.e. a cookie to retrieve values. |
99
|
|
|
* |
100
|
|
|
* @return bool |
101
|
|
|
*/ |
102
|
|
|
public function has_session() { |
103
|
|
|
return isset( $_COOKIE[ $this->_cookie ] ) || $this->_has_cookie || is_user_logged_in(); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Set session expiration. |
108
|
|
|
*/ |
109
|
|
|
public function set_session_expiration() { |
110
|
|
|
$this->_session_expiring = time() + intval( apply_filters( 'wc_session_expiring', 60 * 60 * 47 ) ); // 47 Hours. |
|
|
|
|
111
|
|
|
$this->_session_expiration = time() + intval( apply_filters( 'wc_session_expiration', 60 * 60 * 48 ) ); // 48 Hours. |
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Generate a unique customer ID for guests, or return user ID if logged in. |
116
|
|
|
* |
117
|
|
|
* Uses Portable PHP password hashing framework to generate a unique cryptographically strong ID. |
118
|
|
|
* |
119
|
|
|
* @return int|string |
120
|
|
|
*/ |
121
|
|
|
public function generate_customer_id() { |
122
|
|
|
if ( is_user_logged_in() ) { |
123
|
|
|
return get_current_user_id(); |
124
|
|
|
} else { |
125
|
|
|
require_once( ABSPATH . 'wp-includes/class-phpass.php'); |
126
|
|
|
$hasher = new PasswordHash( 8, false ); |
127
|
|
|
return md5( $hasher->get_random_bytes( 32 ) ); |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Get session cookie. |
133
|
|
|
* |
134
|
|
|
* @return bool|array |
135
|
|
|
*/ |
136
|
|
|
public function get_session_cookie() { |
137
|
|
|
if ( empty( $_COOKIE[ $this->_cookie ] ) || ! is_string( $_COOKIE[ $this->_cookie ] ) ) { |
138
|
|
|
return false; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
list( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $_COOKIE[ $this->_cookie ] ); |
142
|
|
|
|
143
|
|
|
// Validate hash |
144
|
|
|
$to_hash = $customer_id . '|' . $session_expiration; |
145
|
|
|
$hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) ); |
146
|
|
|
|
147
|
|
|
if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) { |
148
|
|
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return array( $customer_id, $session_expiration, $session_expiring, $cookie_hash ); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Get session data. |
156
|
|
|
* |
157
|
|
|
* @return array |
158
|
|
|
*/ |
159
|
|
|
public function get_session_data() { |
160
|
|
|
return $this->has_session() ? (array) $this->get_session( $this->_customer_id, array() ) : array(); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Gets a cache prefix. This is used in session names so the entire cache can be invalidated with 1 function call. |
165
|
|
|
* |
166
|
|
|
* @return string |
167
|
|
|
*/ |
168
|
|
|
private function get_cache_prefix() { |
169
|
|
|
return WC_Cache_Helper::get_cache_prefix( WC_SESSION_CACHE_GROUP ); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Save data. |
174
|
|
|
*/ |
175
|
|
|
public function save_data() { |
176
|
|
|
// Dirty if something changed - prevents saving nothing new |
177
|
|
|
if ( $this->_dirty && $this->has_session() ) { |
178
|
|
|
global $wpdb; |
179
|
|
|
|
180
|
|
|
$wpdb->replace( |
181
|
|
|
$this->_table, |
182
|
|
|
array( |
183
|
|
|
'session_key' => $this->_customer_id, |
184
|
|
|
'session_value' => maybe_serialize( $this->_data ), |
185
|
|
|
'session_expiry' => $this->_session_expiration |
186
|
|
|
), |
187
|
|
|
array( |
188
|
|
|
'%s', |
189
|
|
|
'%s', |
190
|
|
|
'%d' |
191
|
|
|
) |
192
|
|
|
); |
193
|
|
|
|
194
|
|
|
// Set cache |
195
|
|
|
wp_cache_set( $this->get_cache_prefix() . $this->_customer_id, $this->_data, WC_SESSION_CACHE_GROUP, $this->_session_expiration - time() ); |
196
|
|
|
|
197
|
|
|
// Mark session clean after saving |
198
|
|
|
$this->_dirty = false; |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Destroy all session data. |
204
|
|
|
*/ |
205
|
|
|
public function destroy_session() { |
206
|
|
|
// Clear cookie |
207
|
|
|
wc_setcookie( $this->_cookie, '', time() - YEAR_IN_SECONDS, apply_filters( 'wc_session_use_secure_cookie', false ) ); |
208
|
|
|
|
209
|
|
|
$this->delete_session( $this->_customer_id ); |
210
|
|
|
|
211
|
|
|
// Clear cart |
212
|
|
|
wc_empty_cart(); |
213
|
|
|
|
214
|
|
|
// Clear data |
215
|
|
|
$this->_data = array(); |
216
|
|
|
$this->_dirty = false; |
217
|
|
|
$this->_customer_id = $this->generate_customer_id(); |
|
|
|
|
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* When a user is logged out, ensure they have a unique nonce by using the customer/session ID. |
222
|
|
|
* |
223
|
|
|
* @return string |
224
|
|
|
*/ |
225
|
|
|
public function nonce_user_logged_out( $uid ) { |
226
|
|
|
return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Cleanup sessions. |
231
|
|
|
*/ |
232
|
|
|
public function cleanup_sessions() { |
233
|
|
|
global $wpdb; |
234
|
|
|
|
235
|
|
|
if ( ! defined( 'WP_SETUP_CONFIG' ) && ! defined( 'WP_INSTALLING' ) ) { |
236
|
|
|
|
237
|
|
|
// Delete expired sessions |
238
|
|
|
$wpdb->query( $wpdb->prepare( "DELETE FROM $this->_table WHERE session_expiry < %d", time() ) ); |
239
|
|
|
|
240
|
|
|
// Invalidate cache |
241
|
|
|
WC_Cache_Helper::incr_cache_prefix( WC_SESSION_CACHE_GROUP ); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Returns the session. |
247
|
|
|
* |
248
|
|
|
* @param string $customer_id |
249
|
|
|
* @param mixed $default |
250
|
|
|
* @return string|array |
251
|
|
|
*/ |
252
|
|
|
public function get_session( $customer_id, $default = false ) { |
253
|
|
|
global $wpdb; |
254
|
|
|
|
255
|
|
|
if ( defined( 'WP_SETUP_CONFIG' ) ) { |
256
|
|
|
return false; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
// Try get it from the cache, it will return false if not present or if object cache not in use |
260
|
|
|
$value = wp_cache_get( $this->get_cache_prefix() . $customer_id, WC_SESSION_CACHE_GROUP ); |
261
|
|
|
|
262
|
|
|
if ( false === $value ) { |
263
|
|
|
$value = $wpdb->get_var( $wpdb->prepare( "SELECT session_value FROM $this->_table WHERE session_key = %s", $customer_id ) ); |
264
|
|
|
|
265
|
|
|
if ( is_null( $value ) ) { |
266
|
|
|
$value = $default; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
wp_cache_add( $this->get_cache_prefix() . $customer_id, $value, WC_SESSION_CACHE_GROUP, $this->_session_expiration - time() ); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
return maybe_unserialize( $value ); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Delete the session from the cache and database. |
277
|
|
|
* |
278
|
|
|
* @param int $customer_id |
279
|
|
|
*/ |
280
|
|
|
public function delete_session( $customer_id ) { |
281
|
|
|
global $wpdb; |
282
|
|
|
|
283
|
|
|
wp_cache_delete( $this->get_cache_prefix() . $customer_id, WC_SESSION_CACHE_GROUP ); |
284
|
|
|
|
285
|
|
|
$wpdb->delete( |
286
|
|
|
$this->_table, |
287
|
|
|
array( |
288
|
|
|
'session_key' => $customer_id |
289
|
|
|
) |
290
|
|
|
); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Update the session expiry timestamp. |
295
|
|
|
* |
296
|
|
|
* @param string $customer_id |
297
|
|
|
* @param int $timestamp |
298
|
|
|
*/ |
299
|
|
|
public function update_session_timestamp( $customer_id, $timestamp ) { |
300
|
|
|
global $wpdb; |
301
|
|
|
|
302
|
|
|
$wpdb->update( |
303
|
|
|
$this->_table, |
304
|
|
|
array( |
305
|
|
|
'session_expiry' => $timestamp |
306
|
|
|
), |
307
|
|
|
array( |
308
|
|
|
'session_key' => $customer_id |
309
|
|
|
), |
310
|
|
|
array( |
311
|
|
|
'%d' |
312
|
|
|
) |
313
|
|
|
); |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.