1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace EventEspresso\caffeinated\modules\recaptcha_invisible; |
4
|
|
|
|
5
|
|
|
use DomainException; |
6
|
|
|
use EE_Error; |
7
|
|
|
use EE_Form_Section_Proper; |
8
|
|
|
use EE_Invisible_Recaptcha_Input; |
9
|
|
|
use EE_Registration_Config; |
10
|
|
|
use EE_Request; |
11
|
|
|
use EE_Session; |
12
|
|
|
use EventEspresso\core\exceptions\InvalidDataTypeException; |
13
|
|
|
use EventEspresso\core\exceptions\InvalidInterfaceException; |
14
|
|
|
use InvalidArgumentException; |
15
|
|
|
use RuntimeException; |
16
|
|
|
use WP_Error; |
17
|
|
|
|
18
|
|
|
defined('EVENT_ESPRESSO_VERSION') || exit; |
19
|
|
|
|
20
|
|
|
|
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Class ProcessInvisibleRecaptcha |
24
|
|
|
* Provides methods for integrating Google's Invisible reCAPTCHA into EE Forms |
25
|
|
|
* and for verifying the 'g-recaptcha-response' response token |
26
|
|
|
* |
27
|
|
|
* @package EventEspresso\caffeinated\modules\recaptcha_invisible |
28
|
|
|
* @author Brent Christensen |
29
|
|
|
* @since $VID:$ |
30
|
|
|
*/ |
31
|
|
|
class InvisibleRecaptcha |
32
|
|
|
{ |
33
|
|
|
|
34
|
|
|
const URL_GOOGLE_RECAPTCHA_API = 'https://www.google.com/recaptcha/api/siteverify'; |
35
|
|
|
|
36
|
|
|
const SESSION_DATA_KEY_RECAPTCHA_PASSED = 'recaptcha_passed'; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var EE_Registration_Config $config |
40
|
|
|
*/ |
41
|
|
|
private $config; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var EE_Session $session |
45
|
|
|
*/ |
46
|
|
|
private $session; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var boolean $recaptcha_passed |
50
|
|
|
*/ |
51
|
|
|
private $recaptcha_passed; |
52
|
|
|
|
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* InvisibleRecaptcha constructor. |
56
|
|
|
* |
57
|
|
|
* @param EE_Registration_Config $registration_config |
58
|
|
|
* @param EE_Session $session |
59
|
|
|
*/ |
60
|
|
|
public function __construct(EE_Registration_Config $registration_config, EE_Session $session) |
61
|
|
|
{ |
62
|
|
|
$this->config = $registration_config; |
63
|
|
|
$this->session = $session; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @return boolean |
69
|
|
|
*/ |
70
|
|
|
public function useInvisibleRecaptcha() |
71
|
|
|
{ |
72
|
|
|
return $this->config->use_captcha && $this->config->recaptcha_theme === 'invisible'; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param array $input_settings |
78
|
|
|
* @return EE_Invisible_Recaptcha_Input |
79
|
|
|
* @throws InvalidDataTypeException |
80
|
|
|
* @throws InvalidInterfaceException |
81
|
|
|
* @throws InvalidArgumentException |
82
|
|
|
* @throws DomainException |
83
|
|
|
*/ |
84
|
|
|
public function getInput(array $input_settings = array()) |
85
|
|
|
{ |
86
|
|
|
return new EE_Invisible_Recaptcha_Input( |
87
|
|
|
$input_settings, |
88
|
|
|
$this->config |
89
|
|
|
); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @param array $input_settings |
95
|
|
|
* @return string |
96
|
|
|
* @throws EE_Error |
97
|
|
|
* @throws InvalidDataTypeException |
98
|
|
|
* @throws InvalidInterfaceException |
99
|
|
|
* @throws InvalidArgumentException |
100
|
|
|
* @throws DomainException |
101
|
|
|
*/ |
102
|
|
|
public function getInputHtml(array $input_settings = array()) |
103
|
|
|
{ |
104
|
|
|
return $this->getInput($input_settings)->get_html_for_input(); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @param EE_Form_Section_Proper $form |
110
|
|
|
* @param array $input_settings |
111
|
|
|
* @throws EE_Error |
112
|
|
|
* @throws InvalidArgumentException |
113
|
|
|
* @throws InvalidDataTypeException |
114
|
|
|
* @throws InvalidInterfaceException |
115
|
|
|
* @throws DomainException |
116
|
|
|
*/ |
117
|
|
|
public function addToFormSection(EE_Form_Section_Proper $form, array $input_settings = array()) |
118
|
|
|
{ |
119
|
|
|
$form->add_subsections( |
120
|
|
|
array( |
121
|
|
|
'espresso_recaptcha' => $this->getInput($input_settings), |
122
|
|
|
), |
123
|
|
|
null, |
124
|
|
|
false |
125
|
|
|
); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param EE_Request $request |
131
|
|
|
* @return boolean |
132
|
|
|
* @throws RuntimeException |
133
|
|
|
*/ |
134
|
|
|
public function verifyToken(EE_Request $request) |
135
|
|
|
{ |
136
|
|
|
static $previous_recaptcha_response = array(); |
137
|
|
|
$grecaptcha_response = $request->get('g-recaptcha-response'); |
|
|
|
|
138
|
|
|
// if this token has already been verified, then return previous response |
139
|
|
|
if (isset($previous_recaptcha_response[ $grecaptcha_response ])) { |
140
|
|
|
return $previous_recaptcha_response[ $grecaptcha_response ]; |
141
|
|
|
} |
142
|
|
|
// will update to true if everything passes |
143
|
|
|
$previous_recaptcha_response[ $grecaptcha_response ] = false; |
144
|
|
|
$response = wp_safe_remote_post( |
145
|
|
|
InvisibleRecaptcha::URL_GOOGLE_RECAPTCHA_API, |
146
|
|
|
array( |
147
|
|
|
'body' => array( |
148
|
|
|
'secret' => $this->config->recaptcha_privatekey, |
149
|
|
|
'response' => $grecaptcha_response, |
150
|
|
|
'remoteip' => $request->ip_address(), |
|
|
|
|
151
|
|
|
), |
152
|
|
|
) |
153
|
|
|
); |
154
|
|
|
if ($response instanceof WP_Error) { |
|
|
|
|
155
|
|
|
$this->generateError($response->get_error_messages()); |
156
|
|
|
return false; |
157
|
|
|
} |
158
|
|
|
$results = json_decode(wp_remote_retrieve_body($response), true); |
159
|
|
|
if (filter_var($results['success'], FILTER_VALIDATE_BOOLEAN) !== true) { |
160
|
|
|
$errors = array_map( |
161
|
|
|
array($this, 'getErrorCode'), |
162
|
|
|
$results['error-codes'] |
163
|
|
|
); |
164
|
|
|
if(isset($results['challenge_ts'])) { |
165
|
|
|
$errors[] = 'challenge timestamp: ' . $results['challenge_ts'] . '.'; |
166
|
|
|
} |
167
|
|
|
$this->generateError(implode(' ', $errors)); |
168
|
|
|
} |
169
|
|
|
$previous_recaptcha_response[ $grecaptcha_response ] = true; |
170
|
|
|
add_action('shutdown', array($this, 'setSessionData')); |
171
|
|
|
return true; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @param string $error_response |
177
|
|
|
* @return void |
178
|
|
|
* @throws RuntimeException |
179
|
|
|
*/ |
180
|
|
|
public function generateError($error_response = '') |
181
|
|
|
{ |
182
|
|
|
throw new RuntimeException( |
183
|
|
|
sprintf( |
184
|
|
|
esc_html__( |
185
|
|
|
'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. %1$s %2$s Please try again.', |
186
|
|
|
'event_espresso' |
187
|
|
|
), |
188
|
|
|
'<br />', |
189
|
|
|
current_user_can('manage_options') ? $error_response : '' |
190
|
|
|
) |
191
|
|
|
); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param string $error_code |
197
|
|
|
* @return string |
198
|
|
|
*/ |
199
|
|
|
public function getErrorCode(&$error_code) |
200
|
|
|
{ |
201
|
|
|
$error_codes = array( |
202
|
|
|
'missing-input-secret' => 'The secret parameter is missing.', |
203
|
|
|
'invalid-input-secret' => 'The secret parameter is invalid or malformed.', |
204
|
|
|
'missing-input-response' => 'The response parameter is missing.', |
205
|
|
|
'invalid-input-response' => 'The response parameter is invalid or malformed.', |
206
|
|
|
'bad-request' => 'The request is invalid or malformed.', |
207
|
|
|
'timeout-or-duplicate' => 'The request took too long to be sent or was a duplicate of a previous request.', |
208
|
|
|
); |
209
|
|
|
return isset($error_codes[ $error_code ]) ? $error_codes[ $error_code ] : ''; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @return array |
215
|
|
|
* @throws InvalidInterfaceException |
216
|
|
|
* @throws InvalidDataTypeException |
217
|
|
|
* @throws InvalidArgumentException |
218
|
|
|
*/ |
219
|
|
|
public function getLocalizedVars() |
220
|
|
|
{ |
221
|
|
|
return (array) apply_filters( |
222
|
|
|
'FHEE__EventEspresso_caffeinated_modules_recaptcha_invisible_InvisibleRecaptcha__getLocalizedVars__localized_vars', |
223
|
|
|
array( |
224
|
|
|
'siteKey' => $this->config->recaptcha_publickey, |
225
|
|
|
'recaptcha_passed' => $this->recaptchaPassed(), |
226
|
|
|
'wp_debug' => WP_DEBUG, |
227
|
|
|
'disable_submit' => defined('EE_EVENT_QUEUE_BASE_URL'), |
228
|
|
|
) |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* @return boolean |
235
|
|
|
* @throws InvalidInterfaceException |
236
|
|
|
* @throws InvalidDataTypeException |
237
|
|
|
* @throws InvalidArgumentException |
238
|
|
|
*/ |
239
|
|
|
public function recaptchaPassed() |
240
|
|
|
{ |
241
|
|
|
if ($this->recaptcha_passed !== null) { |
242
|
|
|
return $this->recaptcha_passed; |
243
|
|
|
} |
244
|
|
|
// logged in means you have already passed a turing test of sorts |
245
|
|
|
if ($this->useInvisibleRecaptcha() === false || is_user_logged_in()) { |
246
|
|
|
$this->recaptcha_passed = true; |
247
|
|
|
return $this->recaptcha_passed; |
248
|
|
|
} |
249
|
|
|
// was test already passed? |
250
|
|
|
$this->recaptcha_passed = filter_var( |
251
|
|
|
$this->session->get_session_data( |
252
|
|
|
InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED |
253
|
|
|
), |
254
|
|
|
FILTER_VALIDATE_BOOLEAN |
255
|
|
|
); |
256
|
|
|
return $this->recaptcha_passed; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @throws InvalidArgumentException |
262
|
|
|
* @throws InvalidDataTypeException |
263
|
|
|
* @throws InvalidInterfaceException |
264
|
|
|
*/ |
265
|
|
|
public function setSessionData() |
266
|
|
|
{ |
267
|
|
|
$this->session->set_session_data( |
268
|
|
|
array(InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED => true) |
269
|
|
|
); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.