PluginProbe ʕ •ᴥ•ʔ
Advanced Google reCAPTCHA / 5.40
Advanced Google reCAPTCHA v5.40
5.40 5.39 trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.18 1.19 1.20 1.21 1.22 1.23 1.24 1.25 1.26 1.27 1.28 1.29 1.30 1.31 1.32 1.33 1.34 1.35
advanced-google-recaptcha / libs / ajax.php
advanced-google-recaptcha / libs Last commit date
admin.php 4 months ago ajax.php 4 months ago functions.php 1 month ago setup.php 3 months ago stats.php 4 months ago utility.php 4 months ago
ajax.php
508 lines
1 <?php
2
3 /**
4 * WP Captcha
5 * https://getwpcaptcha.com/
6 * (c) WebFactory Ltd, 2022 - 2026, www.webfactoryltd.com
7 */
8
9 class WPCaptcha_AJAX extends WPCaptcha
10 {
11 /**
12 * Run one tool via AJAX call
13 *
14 * @return null
15 */
16 static function ajax_run_tool()
17 {
18 global $wpdb;
19
20 check_ajax_referer('wpcaptcha_run_tool');
21
22 if (!current_user_can('manage_options')) {
23 wp_send_json_error(__('You are not allowed to run this action.', 'advanced-google-recaptcha'));
24 }
25
26 //phpcs:ignore because some calls can be slow for larger logs
27 set_time_limit(300); //phpcs:ignore
28
29 if(!isset($_REQUEST['tool'])){
30 wp_send_json_error(__('Unknown tool.', 'advanced-google-recaptcha'));
31 }
32
33 $tool = sanitize_key(wp_unslash($_REQUEST['tool']));
34
35 $options = WPCaptcha_Setup::get_options();
36
37 $update['last_options_edit'] = current_time('mysql', true);
38 update_option(WPCAPTCHA_OPTIONS_KEY, array_merge($options, $update));
39
40 if ($tool == 'activity_logs') {
41 self::get_activity_logs();
42 } else if ($tool == 'locks_logs') {
43 self::get_locks_logs();
44 } else if ($tool == 'recovery_url') {
45 if (isset($_POST['reset']) && $_POST['reset'] == 'true') {
46 sleep(1);
47 $options['global_unblock_key'] = 'agr' . md5(wp_generate_password(24));
48 update_option(WPCAPTCHA_OPTIONS_KEY, array_merge($options, $update));
49 }
50 wp_send_json_success(array('url' => '<a href="' . site_url('/?wpcaptcha_unblock=' . $options['global_unblock_key']) . '">' . site_url('/?wpcaptcha_unblock=' . $options['global_unblock_key']) . '</a>'));
51 } else if ($tool == 'empty_log') {
52 if(!isset($_POST['log'])){
53 wp_send_json_error(__('Unknown log.', 'advanced-google-recaptcha'));
54 }
55 $log = sanitize_key(wp_unslash($_POST['log']));
56 self::empty_log($log);
57 wp_send_json_success();
58 } else if ($tool == 'unlock_accesslock') {
59 if(!isset($_POST['lock_id'])){
60 wp_send_json_error(__('Unknown ID.', 'advanced-google-recaptcha'));
61 }
62 $lock_id = intval($_POST['lock_id']);
63
64 // phpcs:ignore db call warning as we are using a custom table
65 $wpdb->update( // phpcs:ignore
66 $wpdb->wpcatcha_accesslocks,
67 array(
68 'unlocked' => 1
69 ),
70 array(
71 'accesslock_ID' => $lock_id
72 )
73 );
74 wp_send_json_success(array('id' => $lock_id));
75 } else if ($tool == 'delete_lock_log') {
76 if(!isset($_POST['lock_id'])){
77 wp_send_json_error(__('Unknown ID.', 'advanced-google-recaptcha'));
78 }
79 $lock_id = intval($_POST['lock_id']);
80
81 // phpcs:ignore db call warning as we are using a custom table
82 $wpdb->delete( // phpcs:ignore
83 $wpdb->wpcatcha_accesslocks,
84 array(
85 'accesslock_ID' => $lock_id
86 )
87 );
88 wp_send_json_success(array('id' => $lock_id));
89 } else if ($tool == 'delete_fail_log') {
90 if(!isset($_POST['fail_id'])){
91 wp_send_json_error(__('Unknown ID.', 'advanced-google-recaptcha'));
92 }
93 $fail_id = intval($_POST['fail_id']);
94
95 // phpcs:ignore db call warning as we are using a custom table
96 $wpdb->delete( // phpcs:ignore
97 $wpdb->wpcatcha_login_fails,
98 array(
99 'login_attempt_ID' => $fail_id
100 )
101 );
102 wp_send_json_success(array('id' => $fail_id));
103 } else if ($tool == 'wpcaptcha_dismiss_pointer') {
104 delete_option(WPCAPTCHA_POINTERS_KEY);
105 wp_send_json_success();
106 } else if ($tool == 'verify_captcha') {
107 if(!isset($_POST['captcha_type'])){
108 wp_send_json_error(__('Unknown captcha type.', 'advanced-google-recaptcha'));
109 }
110 $captcha_type = sanitize_key(wp_unslash($_POST['captcha_type']));
111
112 if(!isset($_POST['captcha_site_key'])){
113 wp_send_json_error(__('Unknown site key.', 'advanced-google-recaptcha'));
114 }
115 $captcha_site_key = sanitize_text_field(wp_unslash($_POST['captcha_site_key']));
116
117 if(!isset($_POST['captcha_secret_key'])){
118 wp_send_json_error(__('Unknown secret key.', 'advanced-google-recaptcha'));
119 }
120 $captcha_secret_key = sanitize_text_field(wp_unslash($_POST['captcha_secret_key']));
121
122 if(!isset($_POST['captcha_response'])){
123 wp_send_json_error(__('Unknown response.', 'advanced-google-recaptcha'));
124 }
125 $captcha_response = sanitize_text_field(wp_unslash($_POST['captcha_response']));
126
127 if($captcha_type == 'builtin' && !isset($_POST['captcha_response_token'])){
128 wp_send_json_error(__('Unknown response token.', 'advanced-google-recaptcha'));
129 }
130
131 if(isset($_POST['captcha_response_token'])){
132 $captcha_response_token = sanitize_text_field(wp_unslash($_POST['captcha_response_token']));
133 } else {
134 $captcha_response_token = '';
135 }
136
137 $captcha_result = self::verify_captcha($captcha_type, $captcha_site_key, $captcha_secret_key, $captcha_response, $captcha_response_token);
138 if (is_wp_error($captcha_result)) {
139 wp_send_json_error($captcha_result->get_error_message());
140 }
141 wp_send_json_success($captcha_result);
142 } else {
143 wp_send_json_error(__('Unknown tool.', 'advanced-google-recaptcha'));
144 }
145 die();
146 } // ajax_run_tool
147
148 /**
149 * Get rule row html
150 *
151 * @return string row HTML
152 *
153 * @param array $data with rule settings
154 */
155 static function get_date_time($timestamp)
156 {
157 $interval = current_time('timestamp') - $timestamp;
158 return '<span class="wpcaptcha-dt-small">' . self::humanTiming($interval, true) . '</span><br />' . gmdate('Y/m/d', $timestamp) . ' <span class="wpcaptcha-dt-small">' . gmdate('h:i:s A', $timestamp) . '</span>';
159 }
160
161 static function verify_captcha($type, $site_key, $secret_key, $response, $captcha_response_token = false)
162 {
163 if ($type == 'builtin') {
164 if (wp_hash($response) === $captcha_response_token) {
165 return true;
166 } else {
167 return new WP_Error('wpcaptcha_builtin_captcha_failed', __("<strong>ERROR</strong>: captcha verification failed.<br /><br />Please try again.", 'advanced-google-recaptcha'));
168 }
169 } else if ($type == 'recaptchav2') {
170 if (!isset($response) || empty($response)) {
171 return new WP_Error('wpcaptcha_recaptchav2_not_submitted', __("reCAPTCHA verification failed ", 'advanced-google-recaptcha'));
172 } else {
173 $response = wp_remote_get('https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $response);
174 $response = json_decode($response['body']);
175
176 if ($response->success) {
177 return true;
178 } else {
179 return new WP_Error('wpcaptcha_recaptchav2_failed', __("reCAPTCHA verification failed ", 'advanced-google-recaptcha') . (isset($response->{'error-codes'}) ? ': ' . implode(',', $response->{'error-codes'}) : ''));
180 }
181 }
182 } else if ($type == 'recaptchav3') {
183 if (!isset($response) || empty($response)) {
184 return new WP_Error('wpcaptcha_recaptchav3_not_submitted', __("reCAPTCHA verification failed ", 'advanced-google-recaptcha'));
185 } else {
186 $response = wp_remote_get('https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $response);
187 $response = json_decode($response['body']);
188
189 if ($response->success && $response->score >= 0.5) {
190 return $response->score;
191 } else {
192 return new WP_Error('wpcaptcha_recaptchav2_failed', __("reCAPTCHA verification failed ", 'advanced-google-recaptcha') . (isset($response->{'error-codes'}) ? ': ' . implode(',', $response->{'error-codes'}) : ''));
193 }
194 }
195 }
196 }
197
198 /**
199 * Get human readable timestamp like 2 hours ago
200 *
201 * @return int time
202 *
203 * @param string timestamp
204 */
205 static function humanTiming($time)
206 {
207 $tokens = array(
208 31536000 => 'year',
209 2592000 => 'month',
210 604800 => 'week',
211 86400 => 'day',
212 3600 => 'hour',
213 60 => 'minute',
214 1 => 'second'
215 );
216
217 if ($time < 1) {
218 return 'just now';
219 }
220 foreach ($tokens as $unit => $text) {
221 if ($time < $unit) continue;
222 $numberOfUnits = floor($time / $unit);
223 return $numberOfUnits . ' ' . $text . (($numberOfUnits > 1) ? 's' : '') . ' ago';
224 }
225 }
226
227 static function empty_log($log)
228 {
229 global $wpdb;
230
231 if ($log == 'fails') {
232 $wpdb->query('TRUNCATE TABLE ' . $wpdb->wpcatcha_login_fails);
233 } else {
234 $wpdb->query('TRUNCATE TABLE ' . $wpdb->wpcatcha_accesslocks);
235 }
236 }
237
238 /**
239 * Fetch activity logs and output JSON for datatables
240 *
241 * @return null
242 */
243 static function get_locks_logs() {
244 global $wpdb;
245 check_ajax_referer('wpcaptcha_run_tool');
246
247 if (!current_user_can('manage_options')) {
248 wp_send_json_error(__('You are not allowed to run this action.', 'advanced-google-recaptcha'));
249 }
250
251 $aColumns = array('accesslock_ID', 'unlocked', 'accesslock_date', 'release_date', 'reason', 'accesslock_IP');
252
253 $sLimit = '';
254 if (isset($_GET['iDisplayStart']) && isset($_GET['iDisplayLength']) && $_GET['iDisplayLength'] != '-1') {
255 $limit_offset = intval($_GET['iDisplayStart']);
256 $limit_count = intval($_GET['iDisplayLength']);
257 $sLimit = $wpdb->prepare(" LIMIT %d, %d", $limit_offset, $limit_count);
258 }
259
260 $sOrder = '';
261 $order_clauses = [];
262 if (isset($_GET['iSortCol_0']) && isset($_GET['iSortingCols'])) {
263 for ($i = 0; $i < intval($_GET['iSortingCols']); $i++) {
264 $iSortCol = isset($_GET['iSortCol_' . $i]) ? intval($_GET['iSortCol_' . $i]) : 0;
265 $sSortDir = isset($_GET['sSortDir_' . $i]) ? sanitize_key($_GET['sSortDir_' . $i]) : 'asc';
266
267 if (isset($_GET['bSortable_' . $iSortCol]) && $_GET['bSortable_' . $iSortCol] === "true") {
268 $column = $aColumns[$iSortCol];
269 $dir = ($sSortDir === 'desc') ? 'DESC' : 'ASC';
270 $order_clauses[] = "`$column` $dir";
271 }
272 }
273
274 if (!empty($order_clauses)) {
275 $sOrder = "ORDER BY " . implode(', ', $order_clauses);
276 }
277 }
278
279 $sWhere = '';
280 $where_clauses = [];
281 $query_vars = [];
282
283 if (!empty($_GET['sSearch'])) {
284 $search_term = '%' . $wpdb->esc_like(sanitize_text_field(wp_unslash($_GET['sSearch']))) . '%'; //sanitize_text_field is used to sanitize according to WordPress PCP
285 $sub_clauses = [];
286
287 foreach ($aColumns as $col) {
288 $sub_clauses[] = "`$col` LIKE %s";
289 $query_vars[] = $search_term;
290 }
291
292 $where_clauses[] = '(' . implode(' OR ', $sub_clauses) . ')';
293 }
294
295 for ($i = 0; $i < count($aColumns); $i++) {
296 if (isset($_GET['bSearchable_' . $i]) && $_GET['bSearchable_' . $i] === "true" && !empty($_GET['sSearch_' . $i])) {
297 $search_term = '%' . $wpdb->esc_like(sanitize_text_field(wp_unslash($_GET['sSearch_' . $i]))) . '%';
298 $where_clauses[] = "`" . $aColumns[$i] . "` LIKE %s";
299 $query_vars[] = $search_term;
300 }
301 }
302
303 if (!empty($where_clauses)) {
304 $sWhere = "WHERE " . implode(' AND ', $where_clauses);
305 }
306
307 $sql = "SELECT SQL_CALC_FOUND_ROWS " . implode(", ", $aColumns) .
308 " FROM `{$wpdb->wpcatcha_accesslocks}` $sWhere $sOrder";
309
310 if (!empty($sLimit)) {
311 $sql .= $sLimit;
312 }
313
314 if (!empty($query_vars)) {
315 $prepared_sql = $wpdb->prepare($sql, $query_vars); //phpcs:ignore
316 } else {
317 $prepared_sql = $sql;
318 }
319
320 $rResult = $wpdb->get_results($prepared_sql); //phpcs:ignore
321
322 $iFilteredTotal = $wpdb->get_var("SELECT FOUND_ROWS()"); //phpcs:ignore
323
324 $iTotal = $wpdb->get_var("SELECT COUNT(`accesslock_ID`) FROM `{$wpdb->wpcatcha_accesslocks}`"); //phpcs:ignore
325
326 $output = array(
327 "sEcho" => isset($_GET['sEcho']) ? intval($_GET['sEcho']) : '',
328 "iTotalRecords" => $iTotal,
329 "iTotalDisplayRecords" => $iFilteredTotal,
330 "aaData" => array()
331 );
332
333 foreach ($rResult as $aRow) {
334 $row = array();
335 $row['DT_RowId'] = $aRow->accesslock_ID;
336
337 if (strtotime($aRow->release_date) < time()) {
338 $row['DT_RowClass'] = 'lock_expired';
339 }
340
341 foreach ($aColumns as $col) {
342 if ($col === 'unlocked') {
343 $unblocked = $aRow->$col;
344 if ($unblocked == 0 && strtotime($aRow->release_date) > time()) {
345 $row[] = '<div class="tooltip unlock_accesslock" data-lock-id="' . $aRow->accesslock_ID . '" title="Unlock"><i class="wpcaptcha-icon wpcaptcha-lock"></i></div>';
346 } else {
347 $row[] = '<div class="tooltip unlocked_accesslock" title="Unlock"><i class="wpcaptcha-icon wpcaptcha-unlock"></i></div>';
348 }
349 } elseif ($col === 'accesslock_date') {
350 $row[] = self::get_date_time(strtotime($aRow->$col));
351 } elseif ($col === 'reason') {
352 $row[] = $aRow->$col;
353 } elseif ($col === 'accesslock_IP') {
354 $row[] = '<a href="#" class="open-pro-dialog pro-feature" data-pro-feature="access-log-user-location">Available in PRO</a>';
355 $row[] = '<a href="#" class="open-pro-dialog pro-feature" data-pro-feature="access-log-user-agent">Available in PRO</a>';
356 }
357 }
358
359 $row[] = '<div data-lock-id="' . $aRow->accesslock_ID . '" class="tooltip delete_lock_entry" title="Delete Access Lock?" data-msg-success="Access Lock deleted" data-btn-confirm="Delete Access Lock" data-title="Delete Access Lock?" data-wait-msg="Deleting. Please wait." data-name=""><i class="wpcaptcha-icon wpcaptcha-trash"></i></div>';
360 $output['aaData'][] = $row;
361 }
362
363 @ob_end_clean();
364 header('Cache-Control: no-cache, must-revalidate');
365 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
366 echo json_encode($output);
367 die();
368 }
369
370
371 /**
372 * Fetch activity logs and output JSON for datatables
373 *
374 * @return null
375 */
376 static function get_activity_logs() {
377 global $wpdb;
378 check_ajax_referer('wpcaptcha_run_tool');
379
380 if (!current_user_can('manage_options')) {
381 wp_send_json_error(__('You are not allowed to run this action.', 'advanced-google-recaptcha'));
382 }
383
384 $options = WPCaptcha_Setup::get_options();
385
386 $aColumns = array('login_attempt_ID', 'login_attempt_date', 'failed_user', 'failed_pass', 'login_attempt_IP', 'reason');
387
388 $sLimit = '';
389 if (isset($_GET['iDisplayStart']) && isset($_GET['iDisplayLength']) && $_GET['iDisplayLength'] != '-1') {
390 $sLimit = "LIMIT %d, %d";
391 $limit_offset = intval($_GET['iDisplayStart']);
392 $limit_count = intval($_GET['iDisplayLength']);
393 }
394
395 $sOrder = '';
396 $order_clauses = [];
397 if (isset($_GET['iSortCol_0']) && isset($_GET['iSortingCols'])) {
398 for ($i = 0; $i < intval($_GET['iSortingCols']); $i++) {
399 $iSortCol = isset($_GET['iSortCol_' . $i]) ? intval($_GET['iSortCol_' . $i]) : 0;
400 $sSortDir = isset($_GET['sSortDir_' . $i]) ? sanitize_key($_GET['sSortDir_' . $i]) : 'asc';
401
402 if (isset($_GET['bSortable_' . $iSortCol]) && $_GET['bSortable_' . $iSortCol] === "true") {
403 $column = $aColumns[$iSortCol];
404 $dir = ($sSortDir === 'desc') ? 'DESC' : 'ASC';
405 $order_clauses[] = "`$column` $dir";
406 }
407 }
408
409 if (!empty($order_clauses)) {
410 $sOrder = "ORDER BY " . implode(', ', $order_clauses);
411 }
412 }
413
414 // filtering
415 $sWhere = '';
416 $where_clauses = [];
417 $query_vars = [];
418
419 if (!empty($_GET['sSearch'])) {
420 $search_term = '%' . $wpdb->esc_like(sanitize_text_field(wp_unslash($_GET['sSearch']))) . '%'; //sanitize_text_field is used to sanitize according to WordPress PCP
421 $sub_clauses = [];
422
423 foreach ($aColumns as $col) {
424 $sub_clauses[] = "`$col` LIKE %s";
425 $query_vars[] = $search_term;
426 }
427
428 $where_clauses[] = '(' . implode(' OR ', $sub_clauses) . ')';
429 }
430
431 for ($i = 0; $i < count($aColumns); $i++) {
432 if (isset($_GET['bSearchable_' . $i]) && $_GET['bSearchable_' . $i] === "true" && !empty($_GET['sSearch_' . $i])) {
433 $search_term = '%' . $wpdb->esc_like(sanitize_text_field(wp_unslash($_GET['sSearch_' . $i]))) . '%';
434 $where_clauses[] = "`" . $aColumns[$i] . "` LIKE %s";
435 $query_vars[] = $search_term;
436 }
437 }
438
439 if (!empty($where_clauses)) {
440 $sWhere = "WHERE " . implode(' AND ', $where_clauses);
441 }
442
443 // build query
444 $sql = "SELECT SQL_CALC_FOUND_ROWS " . implode(", ", $aColumns) .
445 " FROM " . $wpdb->wpcatcha_login_fails . " $sWhere $sOrder";
446
447 if (!empty($sLimit)) {
448 $sql .= " " . $wpdb->prepare("LIMIT %d, %d", $limit_offset, $limit_count);//phpcs:ignore
449 }
450
451 //phpcs:ignore because the query parts are dynamic based on the number of columns
452 if (!empty($query_vars)) {
453 $prepared_sql = $wpdb->prepare($sql, $query_vars);//phpcs:ignore
454 } else {
455 $prepared_sql = $sql;
456 }
457
458 $rResult = $wpdb->get_results($prepared_sql); //phpcs:ignore
459
460 // filtered count
461 $iFilteredTotal = $wpdb->get_var("SELECT FOUND_ROWS()"); //phpcs:ignore
462
463 // total count
464 //phpcs: no need to prepare, $sIndexColumn
465 $iTotal = $wpdb->get_var("SELECT COUNT(`login_attempt_ID`) FROM {$wpdb->wpcatcha_login_fails}"); //phpcs:ignore
466
467 // output formatting
468 $output = array(
469 "sEcho" => isset($_GET['sEcho']) ? intval($_GET['sEcho']) : '',
470 "iTotalRecords" => $iTotal,
471 "iTotalDisplayRecords" => $iFilteredTotal,
472 "aaData" => array()
473 );
474
475 foreach ($rResult as $aRow) {
476 $row = array();
477 $row['DT_RowId'] = $aRow->login_attempt_ID;
478
479 foreach ($aColumns as $col) {
480 if ($col == 'login_attempt_date') {
481 $row[] = self::get_date_time(strtotime($aRow->$col));
482 } elseif ($col == 'failed_user') {
483 $failed_login = '<strong>User:</strong> ' . htmlspecialchars($aRow->failed_user) . '<br />';
484 if ($options['log_passwords'] == 1) {
485 $failed_login .= '<strong>Pass:</strong> ' . htmlspecialchars($aRow->failed_pass) . '<br />';
486 }
487 $row[] = $failed_login;
488 } elseif ($col == 'login_attempt_IP') {
489 $row[] = '<a href="#" class="open-pro-dialog pro-feature" data-pro-feature="fail-log-user-location">Available in PRO</a>';
490 $row[] = '<a href="#" class="open-pro-dialog pro-feature" data-pro-feature="fail-log-user-agent">Available in PRO</a>';
491 } elseif ($col == 'reason') {
492 $row[] = WPCaptcha_Functions::pretty_fail_errors($aRow->$col);
493 }
494 }
495
496 $row[] = '<div data-failed-id="' . $aRow->login_attempt_ID . '" class="tooltip delete_failed_entry" title="Delete failed login attempt log entry" data-msg-success="Failed login attempt log entry deleted" data-btn-confirm="Delete failed login attempt log entry" data-title="Delete failed login attempt log entry" data-wait-msg="Deleting. Please wait." data-name=""><i class="wpcaptcha-icon wpcaptcha-trash"></i></div>';
497
498 $output['aaData'][] = $row;
499 }
500
501 @ob_end_clean();
502 header('Cache-Control: no-cache, must-revalidate');
503 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
504 echo json_encode($output);
505 die();
506 }
507 } // class
508