PluginProbe
wpForo Forum / 3.0.1
wpForo Forum v3.0.1
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / classes / API.php

API.php in wpForo Forum 3.0.1, at classes/API.php

861 lines 31.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\classes;
4
5 use WP_Error;
6
7 // Exit if accessed directly
8 if( ! defined( 'ABSPATH' ) ) exit;
9
10 class API {
11 public $locale = 'en_US';
12 public $locale_iso = 'en';
13 public $fb_local = [
14 'af_ZA',
15 'ar_AR',
16 'az_AZ',
17 'be_BY',
18 'bg_BG',
19 'bn_IN',
20 'bs_BA',
21 'ca_ES',
22 'cs_CZ',
23 'cy_GB',
24 'da_DK',
25 'de_DE',
26 'el_GR',
27 'en_US',
28 'en_GB',
29 'eo_EO',
30 'es_ES',
31 'es_LA',
32 'et_EE',
33 'eu_ES',
34 'fa_IR',
35 'fb_LT',
36 'fi_FI',
37 'fo_FO',
38 'fr_FR',
39 'fr_CA',
40 'fy_NL',
41 'ga_IE',
42 'gl_ES',
43 'he_IL',
44 'hi_IN',
45 'hr_HR',
46 'hu_HU',
47 'hy_AM',
48 'id_ID',
49 'is_IS',
50 'it_IT',
51 'ja_JP',
52 'ka_GE',
53 'km_KH',
54 'ko_KR',
55 'ku_TR',
56 'la_VA',
57 'lt_LT',
58 'lv_LV',
59 'mk_MK',
60 'ml_IN',
61 'ms_MY',
62 'nb_NO',
63 'ne_NP',
64 'nl_NL',
65 'nn_NO',
66 'pa_IN',
67 'pl_PL',
68 'ps_AF',
69 'pt_PT',
70 'pt_BR',
71 'ro_RO',
72 'ru_RU',
73 'sk_SK',
74 'sl_SI',
75 'sq_AL',
76 'sr_RS',
77 'sv_SE',
78 'sw_KE',
79 'ta_IN',
80 'te_IN',
81 'th_TH',
82 'tl_PH',
83 'tr_TR',
84 'uk_UA',
85 'vi_VN',
86 'zh_CN',
87 'zh_HK',
88 'zh_TW',
89 ];
90 public $tw_local = [
91 'en',
92 'ar',
93 'bn',
94 'cs',
95 'da',
96 'de',
97 'el',
98 'es',
99 'fa',
100 'fi',
101 'fil',
102 'fr',
103 'he',
104 'hi',
105 'hu',
106 'id',
107 'it',
108 'ja',
109 'ko',
110 'msa',
111 'nl',
112 'no',
113 'pl',
114 'pt',
115 'ro',
116 'ru',
117 'sv',
118 'th',
119 'tr',
120 'uk',
121 'ur',
122 'vi',
123 'zh-cn',
124 'zh-tw',
125 ];
126 public $ok_local = [ "ru", "en", "uk", "hy", "mo", "ro", "kk", "uz", "az", "tr" ];
127
128 public function __construct() {
129 add_action( 'wpforo_after_init', function() {
130 if( ! wpforo_is_admin() ) {
131 $this->init_wp_recaptcha();
132 $this->hooks();
133 }
134 } );
135 }
136
137 private function hooks() {
138 $template = WPF()->current_object['template'];
139
140 ###############################################################################
141 ############### X.com & Social Share API ####################################
142 ###############################################################################
143
144 if( is_wpforo_page() ) {
145 if( apply_filters( 'wpforo_api_tw_load_wjs', true ) && wpforo_setting( 'social', 'sb', 'tw' ) ) {
146 add_action( 'wpforo_top_hook', [ $this, 'tw_wjs' ], 11 );
147 }
148 if( apply_filters( 'wpforo_api_vk_load_js', true ) && wpforo_setting( 'social', 'sb', 'vk' ) ) {
149 add_action( 'wpforo_top_hook', [ $this, 'vk_js' ], 13 );
150 }
151 }
152
153 ###############################################################################
154 ############### reCAPTCHA API #################################################
155 ###############################################################################
156
157 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
158 $secret_key = wpforo_setting( 'recaptcha', 'secret_key' );
159
160 if( ! is_user_logged_in() && $site_key && $secret_key ) {
161
162 $wpf_reg_form = wpforo_setting( 'recaptcha', 'wpf_reg_form' );
163 $wpf_login_form = wpforo_setting( 'recaptcha', 'wpf_login_form' );
164 $wpf_lostpass_form = wpforo_setting( 'recaptcha', 'wpf_lostpass_form' );
165
166 add_filter( 'script_loader_tag', [ &$this, 'rc_enqueue_async' ], 10, 3 );
167
168 //Verification Hooks: Login / Register / Reset Pass
169 if( $wpf_login_form ) add_filter( 'wp_authenticate_user', [ $this, 'rc_verify_wp_login' ], 15, 2 );
170 if( $wpf_reg_form ) add_filter( 'registration_errors', [ $this, 'rc_verify_wp_register' ], 10, 3 );
171 if( $wpf_lostpass_form ) add_action( 'lostpassword_post', [ $this, 'rc_verify_wp_lostpassword' ], 10 );
172
173 //Load reCAPTCHA API on wpForo pages: Login / Register / Reset Pass
174 if( in_array( $template, [ 'login', 'register', 'lostpassword' ], true ) ) {
175 if( $wpf_reg_form || $wpf_login_form || $wpf_lostpass_form ) {
176 add_action( 'wp_enqueue_scripts', [ $this, 'rc_enqueue' ] );
177 }
178 }
179
180 //Load reCAPTCHA Widget wpForo forms: Login / Register / Reset Pass
181 if( $wpf_login_form && $template === 'login' ) add_action( 'login_form', [ $this, 'rc_widget' ] );
182 if( $wpf_reg_form && $template === 'register' ) add_action( 'register_form', [ $this, 'rc_widget' ] );
183 if( $wpf_lostpass_form && $template === 'lostpassword' ) {
184 add_action( 'lostpassword_form', [ $this, 'rc_widget' ] );
185 }
186
187 //Load reCAPTCHA API and Widget for Topic and Post Editor
188 if( in_array( $template, [ 'forum', 'topic', 'post', 'add-topic' ], true ) ) {
189 add_action( 'wp_enqueue_scripts', [ $this, 'rc_enqueue' ] );
190 add_action( 'wpforo_verify_form_end', [ $this, 'rc_verify' ] );
191 if( wpforo_setting( 'recaptcha', 'topic_editor' ) ) {
192 add_action( 'wpforo_topic_form_extra_fields_after', [ $this, 'rc_widget' ] );
193 }
194 if( wpforo_setting( 'recaptcha', 'post_editor' ) ) {
195 add_action( 'wpforo_reply_form_extra_fields_after', [ $this, 'rc_widget' ] );
196 add_action( 'wpforo_portable_form_extra_fields_after', [ $this, 'rc_widget' ] );
197 }
198 }
199 }
200
201 ###############################################################################
202 }
203
204 private function init_wp_recaptcha() {
205 $template = WPF()->current_object['template'];
206 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
207 $secret_key = wpforo_setting( 'recaptcha', 'secret_key' );
208
209 if( ! is_user_logged_in() && $site_key && $secret_key ) {
210 $reg_form = wpforo_setting( 'recaptcha', 'reg_form' );
211 $login_form = wpforo_setting( 'recaptcha', 'login_form' );
212 $lostpass_form = wpforo_setting( 'recaptcha', 'lostpass_form' );
213
214 //Verification Hooks: Login / Register / Reset Pass
215 if( $login_form ) add_filter( 'wp_authenticate_user', [ $this, 'rc_verify_wp_login' ], 15, 2 );
216 if( $reg_form ) add_filter( 'registration_errors', [ $this, 'rc_verify_wp_register' ], 10, 3 );
217 if( $lostpass_form ) add_action( 'lostpassword_post', [ $this, 'rc_verify_wp_lostpassword' ], 10 );
218
219 //Load reCAPTCHA API and Widget on wp-login.php
220 if( $reg_form || $login_form || $lostpass_form ) {
221 add_action( 'login_enqueue_scripts', [ $this, 'rc_enqueue' ] );
222 add_action( 'login_enqueue_scripts', [ $this, 'rc_enqueue_css' ] );
223 if( $login_form && $template !== 'login' ) add_action( 'login_form', [ $this, 'rc_widget' ] );
224 if( $reg_form && $template !== 'register' ) add_action( 'register_form', [ $this, 'rc_widget' ] );
225 if( $lostpass_form && $template !== 'lostpassword' ) {
226 add_action( 'lostpassword_form', [ $this, 'rc_widget' ] );
227 }
228 }
229 }
230 }
231
232 public function local( $api ) {
233 $wplocal = get_locale();
234 $wplocal_iso = substr( $wplocal, 0, 2 );
235
236 if( $api === 'fb' ) {
237 if( in_array( $wplocal, $this->fb_local ) ) {
238 return $wplocal;
239 } else {
240 return $this->locale;
241 }
242 } elseif( $api === 'tw' ) {
243 if( in_array( $wplocal_iso, $this->tw_local ) ) {
244 return $wplocal_iso;
245 } else {
246 return $this->locale_iso;
247 }
248 } elseif( $api === 'vk' ) {
249 return $wplocal_iso;
250 } elseif( $api === 'ok' ) {
251 if( in_array( $wplocal_iso, $this->ok_local ) ) {
252 return $wplocal_iso;
253 } else {
254 return $this->locale_iso;
255 }
256 }
257
258 return $wplocal;
259 }
260
261
262 public function fb_share_button( $url = '', $type = 'custom', $text = '' ) {
263 if( ! wpforo_setting( 'social', 'sb', 'fb' ) ) return;
264 $url = $url ?: WPF()->current_url;
265 // Facebook deprecated custom parameters (quote, title, etc.)
266 // Content is now pulled from Open Graph meta tags on the shared URL
267 $share_url = 'https://www.facebook.com/sharer/sharer.php?u=' . urlencode( (string) $url );
268 if( $type === 'custom' ) { ?>
269 <span class="wpforo-share-button wpf-fb" data-wpfurl="<?php echo esc_attr( $url ) ?>"
270 title="<?php wpforo_phrase( 'Share to Facebook' ); ?>">
271 <?php if( wpforo_setting( 'social', 'sb_icon' ) === 'figure' ): ?>
272 <i class="fab fa-facebook-f" aria-hidden="true"></i>
273 <?php elseif( wpforo_setting( 'social', 'sb_icon' ) === 'square' ): ?>
274 <i class="fab fa-facebook-square" aria-hidden="true"></i>
275 <?php else: ?>
276 <i class="fab fa-facebook" aria-hidden="true"></i>
277 <?php endif; ?>
278 </span>
279 <?php
280 } else {
281 ?>
282 <div class="wpf-sbw wpf-sbw-fb">
283 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
284 <a target="_blank" href="<?php echo esc_url( $share_url ) ?>"
285 rel="nofollow"><?php wpforo_phrase( 'Share' ); ?></a>
286 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
287 <a class="wpf-sb-button wpf-fb" href="<?php echo esc_url( $share_url ) ?>" target="_blank" rel="nofollow">
288 <i class="fab fa-facebook-f" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
289 </a>
290 <?php else: ?>
291 <a class="wpf-sb-button wpf-sb-icon wpf-fb" href="<?php echo esc_url( $share_url ) ?>" target="_blank" rel="nofollow">
292 <i class="fab fa-facebook-f" aria-hidden="true"></i>
293 </a>
294 <?php endif; ?>
295 </div>
296 <?php
297 }
298 }
299
300 public function tw_wjs() {
301 ?>
302 <script type="text/javascript">window.twttr = (function (d, s, id) {
303 var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {};
304 if (d.getElementById(id)) return t;
305 js = d.createElement(s);
306 js.id = id;
307 js.src = 'https://platform.twitter.com/widgets.js';
308 fjs.parentNode.insertBefore(js, fjs);
309 t._e = [];
310 t.ready = function (f) { t._e.push(f); };
311 return t;
312 }(document, 'script', 'twitter-wjs'));</script>
313 <?php
314 }
315
316 public function tw_share_button( $url = '', $type = 'custom', $text = '' ) {
317 if( ! wpforo_setting( 'social', 'sb', 'tw' ) ) return;
318 $url = $url ?: WPF()->current_url;
319 $n_url = strlen( (string) $url );
320 $n_text = 280 - $n_url;
321 $text = $text ?: wpfval( WPF()->current_object, 'og_text' );
322 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), $n_text, false ) );
323 if( $type == 'custom' ) { ?>
324 <a class="wpforo-share-button wpf-tw"
325 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode( (string) $url ) ?>"
326 title="<?php wpforo_phrase( 'Tweet this post' ); ?>" rel="nofollow">
327 <?php if( wpforo_setting( 'social', 'sb_icon' ) === 'figure' ): ?>
328 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
329 <?php elseif( wpforo_setting( 'social', 'sb_icon' ) === 'square' ): ?>
330 <i class="fa-brands fa-x-twitter-square" aria-hidden="true"></i>
331 <?php else: ?>
332 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
333 <?php endif; ?>
334 </a>
335 <?php
336 } else { ?>
337 <div class="wpf-sbw wpf-sbw-tw">
338 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
339 <a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button"
340 data-lang="<?php $this->local( 'tw' ) ?>" data-show-count="true" rel="nofollow"><?php wpforo_phrase(
341 'Tweet'
342 ); ?></a>
343 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
344 <a class="wpf-sb-button wpf-tw"
345 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode(
346 (string) $url
347 ) ?>" rel="nofollow">
348 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i> <span><?php wpforo_phrase(
349 'Tweet'
350 ) ?></span>
351 </a>
352 <?php else: ?>
353 <a class="wpf-sb-button wpf-sb-icon wpf-tw"
354 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode(
355 (string) $url
356 ) ?>" rel="nofollow">
357 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
358 </a>
359 <?php endif; ?>
360 </div>
361 <?php
362 }
363 }
364
365 public function vk_js() {
366 ?>
367 <script type="text/javascript" src="https://vk.com/js/api/share.js?95" charset="windows-1251"></script>
368 <?php
369 }
370
371 public function wapp_share_button( $url = '', $type = 'custom', $text = '' ) {
372 if( ! wpforo_setting( 'social', 'sb', 'wapp' ) ) return;
373 $url = $url ?: WPF()->current_url;
374 $domain = wp_is_mobile() ? 'https://api.whatsapp.com' : 'https://web.whatsapp.com';
375 $text = $text ?: ( wpfval( WPF()->current_object, 'og_text' ) ? WPF()->current_object['og_text'] : WPF()->board->get_current( 'settings' )['title'] );
376 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 100, false ) ) . ' URL: ' . urlencode( (string) $url );
377 if( $type === 'custom' ) { ?>
378 <a class="wpforo-share-button wpf-wapp" href="<?php echo $domain ?>/send?text=<?php echo $text ?>"
379 title="<?php wpforo_phrase( 'Share to WhatsApp' ); ?>" target="_blank"
380 data-action="share/whatsapp/share" rel="nofollow">
381 <i class="fab fa-whatsapp" aria-hidden="true"></i>
382 </a>
383 <?php
384 } else { ?>
385 <div class="wpf-sbw wpf-sbw-wapp">
386 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
387 <!-- WhatsApp is not available -->
388 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
389 <a class="wpf-sb-button wpf-wapp" href="<?php echo $domain ?>/send?text=<?php echo $text ?>"
390 target="_blank" data-action="share/whatsapp/share" rel="nofollow">
391 <i class="fab fa-whatsapp" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
392 </a>
393 <?php else: ?>
394 <a class="wpf-sb-button wpf-sb-icon wpf-wapp"
395 href="<?php echo $domain ?>/send?text=<?php echo $text ?>" target="_blank"
396 data-action="share/whatsapp/share" rel="nofollow">
397 <i class="fab fa-whatsapp" aria-hidden="true"></i>
398 </a>
399 <?php endif; ?>
400 </div>
401 <?php
402 }
403 }
404
405 public function lin_share_button( $url = '', $type = 'custom', $text = '' ) {
406 if( ! wpforo_setting( 'social', 'sb', 'lin' ) ) return;
407 $url = $url ?: WPF()->current_url;
408 $title = (string) wpfval( WPF()->current_object, 'topic', 'title' );
409 $text = $text ?: ( wpfval( WPF()->current_object, 'og_text' ) ? WPF()->current_object['og_text'] : WPF()->board->get_current(
410 'settings'
411 )['title'] );
412 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 500, false ) );
413 if( $type == 'custom' ) { ?>
414 <a class="wpforo-share-button wpf-lin"
415 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
416 (string) $url
417 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>"
418 title="<?php wpforo_phrase( 'Share to LinkedIn' ); ?>" target="_blank" rel="nofollow">
419 <i class="fab fa-linkedin-in" aria-hidden="true"></i>
420 </a>
421 <?php
422 } else { ?>
423 <div class="wpf-sbw wpf-sbw-lin">
424 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
425 <!-- LinkedIn is not available -->
426 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
427 <a class="wpf-sb-button wpf-lin"
428 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
429 (string) $url
430 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>" target="_blank" rel="nofollow">
431 <i class="fab fa-linkedin-in" aria-hidden="true"></i> <span><?php wpforo_phrase(
432 'Share'
433 ) ?></span>
434 </a>
435 <?php else: ?>
436 <a class="wpf-sb-button wpf-sb-icon wpf-lin"
437 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
438 (string) $url
439 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>" target="_blank" rel="nofollow">
440 <i class="fab fa-linkedin-in" aria-hidden="true"></i>
441 </a>
442 <?php endif; ?>
443 </div>
444 <?php
445 }
446 }
447
448 public function vk_share_button( $url = '', $type = 'custom', $text = '' ) {
449 if( ! wpforo_setting( 'social', 'sb', 'vk' ) ) return;
450 $url = $url ?: WPF()->current_url;
451 $text = $text ?: wpfval( WPF()->current_object, 'og_text' );
452 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 500, false ) );
453 if( $type === 'custom' ) { ?>
454 <a class="wpforo-share-button wpf-vk" onclick="return VK.Share.click(0, this);"
455 href="https://vk.com/share.php?url=<?php echo urlencode( (string) $url ) ?>&description=<?php echo $text ?>"
456 title="<?php wpforo_phrase( 'Share to VK' ); ?>" target="_blank" rel="nofollow">
457 <i class="fab fa-vk" aria-hidden="true"></i>
458 </a>
459 <?php
460 } else { ?>
461 <div class="wpf-sbw wpf-sbw-vk">
462 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
463 <script type="text/javascript">document.write(VK.Share.button(false, {
464 type: 'round',
465 text: "<?php wpforo_phrase( 'Share' ); ?>",
466 }));</script>
467 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
468 <a class="wpf-sb-button wpf-vk" onclick="return VK.Share.click(0, this);"
469 href="https://vk.com/share.php?url=<?php echo urlencode(
470 (string) $url
471 ) ?>&description=<?php echo $text ?>" target="_blank" rel="nofollow">
472 <i class="fab fa-vk" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
473 </a>
474 <?php else: ?>
475 <a class="wpf-sb-button wpf-sb-icon wpf-vk" onclick="return VK.Share.click(0, this);"
476 href="https://vk.com/share.php?url=<?php echo urlencode(
477 (string) $url
478 ) ?>&description=<?php echo $text ?>" target="_blank" rel="nofollow">
479 <i class="fab fa-vk" aria-hidden="true"></i>
480 </a>
481 <?php endif; ?>
482 </div>
483 <?php
484 }
485 }
486
487
488 public function share_toggle( $url = '', $text = '', $type = 'custom' ) {
489 WPF()->api->fb_share_button( $url, $type );
490 WPF()->api->tw_share_button( $url, $type, $text );
491 WPF()->api->wapp_share_button( $url, $type, $text );
492 WPF()->api->lin_share_button( $url, $type, $text );
493 WPF()->api->vk_share_button( $url, $type, $text );
494 }
495
496 public function share_buttons( $url = '', $type = 'default', $text = '' ) {
497 $template = wpfval( WPF()->current_object, 'template' );
498 $exclude = [ 'lostpassword', 'resetpassword' ];
499 if( $template && ! in_array( $template, $exclude ) ) {
500 WPF()->api->fb_share_button( $url, $type );
501 WPF()->api->tw_share_button( $url, $type, $text );
502 WPF()->api->wapp_share_button( $url, $type, $text );
503 WPF()->api->lin_share_button( $url, $type, $text );
504 WPF()->api->vk_share_button( $url, $type, $text );
505 }
506 }
507
508 public function rc_enqueue() {
509 $version = wpforo_setting( 'recaptcha', 'version' );
510 $theme = wpforo_setting( 'recaptcha', 'theme' );
511 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
512
513 // reCAPTCHA v3 uses a different script URL and approach
514 if( $version === 'v3' ) {
515 wp_register_script(
516 'wpforo_recaptcha',
517 'https://www.google.com/recaptcha/api.js?render=' . $site_key
518 );
519 wp_add_inline_script(
520 'wpforo_recaptcha',
521 "var wpForoReCaptchaV3Execute = function(action){
522 return new Promise(function(resolve, reject){
523 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.execute === 'function' ){
524 grecaptcha.ready(function(){
525 grecaptcha.execute('" . esc_js( $site_key ) . "', {action: action}).then(function(token){
526 resolve(token);
527 }).catch(function(error){
528 reject(error);
529 });
530 });
531 } else {
532 reject('reCAPTCHA not loaded');
533 }
534 });
535 };
536 var wpForoReCaptchaV3Submit = function(form){
537 var btn = form.querySelector('input[type=\"submit\"], button[type=\"submit\"]');
538 if( btn && btn.name && !form.querySelector('input[type=\"hidden\"][name=\"' + btn.name + '\"]') ){
539 var h = document.createElement('input');
540 h.type = 'hidden';
541 h.name = btn.name;
542 h.value = btn.value || '1';
543 form.appendChild(h);
544 }
545 form.submit();
546 };
547 var wpForoReCaptchaV3Init = function(){
548 var forms = document.querySelectorAll('form[data-wpforo-recaptcha-v3]');
549 forms.forEach(function(form){
550 form.addEventListener('submit', function(e){
551 var tokenInput = form.querySelector('input[name=\"g-recaptcha-response\"]');
552 if( tokenInput && !tokenInput.value ){
553 e.preventDefault();
554 var action = form.getAttribute('data-wpforo-recaptcha-action') || 'wpforo_form';
555 wpForoReCaptchaV3Execute(action).then(function(token){
556 tokenInput.value = token;
557 wpForoReCaptchaV3Submit(form);
558 }).catch(function(error){
559 console.error('reCAPTCHA error:', error);
560 wpForoReCaptchaV3Submit(form);
561 });
562 }
563 });
564 });
565 };
566 if( document.readyState === 'loading' ){
567 document.addEventListener('DOMContentLoaded', wpForoReCaptchaV3Init);
568 } else {
569 wpForoReCaptchaV3Init();
570 }"
571 );
572 } elseif( $version === 'v2_invisible' ) {
573 // reCAPTCHA v2 Invisible
574 wp_register_script(
575 'wpforo_recaptcha',
576 'https://www.google.com/recaptcha/api.js?onload=wpForoReCallback&render=explicit'
577 );
578 wp_add_inline_script(
579 'wpforo_recaptcha',
580 "var wpForoReWidgetIds = {};
581 var wpForoReCallback = function(){
582 setTimeout(function () {
583 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function' ){
584 var rc_widgets = document.getElementsByClassName('wpforo_recaptcha_widget');
585 if( rc_widgets.length ){
586 var i;
587 for (i = 0; i < rc_widgets.length; i++) {
588 if( rc_widgets[i].firstElementChild === null ){
589 rc_widgets[i].innerHTML = '';
590 var form = rc_widgets[i].closest('form');
591 var widgetId = grecaptcha.render(
592 rc_widgets[i], {
593 'sitekey': '" . esc_js( $site_key ) . "',
594 'theme': '" . esc_js( $theme ) . "',
595 'size': 'invisible',
596 'callback': function(token){
597 if( form ){
598 var tokenInput = form.querySelector('input[name=\"g-recaptcha-response\"]');
599 if( !tokenInput ){
600 tokenInput = document.createElement('input');
601 tokenInput.type = 'hidden';
602 tokenInput.name = 'g-recaptcha-response';
603 form.appendChild(tokenInput);
604 }
605 tokenInput.value = token;
606 form.submit();
607 }
608 }
609 }
610 );
611 if( form ){
612 wpForoReWidgetIds[form.id || i] = widgetId;
613 form.addEventListener('submit', function(e){
614 var formId = this.id || Array.prototype.indexOf.call(document.forms, this);
615 if( wpForoReWidgetIds[formId] !== undefined ){
616 var tokenInput = this.querySelector('input[name=\"g-recaptcha-response\"]');
617 if( !tokenInput || !tokenInput.value ){
618 e.preventDefault();
619 grecaptcha.execute(wpForoReWidgetIds[formId]);
620 }
621 }
622 });
623 }
624 }
625 }
626 }
627 }
628 }, 800);
629 }"
630 );
631 } else {
632 // reCAPTCHA v2 Checkbox (default)
633 wp_register_script(
634 'wpforo_recaptcha',
635 'https://www.google.com/recaptcha/api.js?onload=wpForoReCallback&render=explicit'
636 );
637 wp_add_inline_script(
638 'wpforo_recaptcha',
639 "var wpForoReCallback = function(){
640 setTimeout(function () {
641 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function' ){
642 var rc_widgets = document.getElementsByClassName('wpforo_recaptcha_widget');
643 if( rc_widgets.length ){
644 var i;
645 for (i = 0; i < rc_widgets.length; i++) {
646 if( rc_widgets[i].firstElementChild === null ){
647 rc_widgets[i].innerHTML = '';
648 grecaptcha.render(
649 rc_widgets[i], { 'sitekey': '" . esc_js( $site_key ) . "', 'theme': '" . esc_js( $theme ) . "' }
650 );
651 }
652 }
653 }
654 }
655 }, 800);
656 }"
657 );
658 }
659 wp_enqueue_script( 'wpforo_recaptcha' );
660 }
661
662 public function rc_enqueue_async( $tag, $handle ) {
663 if( $handle === 'wpforo_recaptcha' ) return str_replace( '<script', '<script async defer', $tag );
664
665 return $tag;
666 }
667
668 public function rc_enqueue_css() {
669 $version = wpforo_setting( 'recaptcha', 'version' );
670
671 // Only add CSS for v2 checkbox (visible widget)
672 // v2 invisible and v3 don't need widget styling
673 if( $version !== 'v2_checkbox' ) {
674 return;
675 }
676
677 wp_register_style( 'wpforo-rc-style', false );
678 wp_enqueue_style( 'wpforo-rc-style' );
679 $custom_css = ".wpforo_recaptcha_widget{ -webkit-transform:scale(0.9); transform:scale(0.9); -webkit-transform-origin:left 0; transform-origin:left 0; }";
680 wp_add_inline_style( 'wpforo-rc-style', $custom_css );
681 }
682
683 public function rc_widget( $action = 'wpforo_form' ) {
684 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
685 $version = wpforo_setting( 'recaptcha', 'version' );
686
687 if( $site_key ) {
688 if( $version === 'v3' ) {
689 // reCAPTCHA v3 - hidden token input, form gets data attributes
690 echo '<input type="hidden" name="g-recaptcha-response" value="" />';
691 echo '<input type="hidden" name="g-recaptcha-version" value="v3" />';
692 // Add JS to mark the parent form for v3 processing
693 echo "\r\n<script>(function(){
694 var input = document.currentScript.previousElementSibling.previousElementSibling;
695 var form = input.closest('form');
696 if(form){
697 form.setAttribute('data-wpforo-recaptcha-v3', '1');
698 form.setAttribute('data-wpforo-recaptcha-action', '" . esc_js( $action ) . "');
699 }
700 })();</script>";
701 } else {
702 // reCAPTCHA v2 (checkbox or invisible) - standard widget div
703 echo '<div class="wpforo_recaptcha_widget"></div><div class="wpf-cl"></div>';
704 echo "\r\n<script>if(typeof wpForoReCallback === 'function') wpForoReCallback();</script>";
705 }
706 }
707 }
708
709 public function _rc_check() {
710 if( isset( $_POST['g-recaptcha-response'] ) && ! empty( $_POST['g-recaptcha-response'] ) ) {
711 $secret_key = wpforo_setting( 'recaptcha', 'secret_key' );
712 $version = wpforo_setting( 'recaptcha', 'version' );
713
714 // Use POST method for verification (recommended by Google)
715 $response = wp_remote_post(
716 'https://www.google.com/recaptcha/api/siteverify',
717 [
718 'body' => [
719 'secret' => $secret_key,
720 'response' => sanitize_text_field( $_POST['g-recaptcha-response'] ),
721 'remoteip' => WPF()->current_user_ip,
722 ],
723 ]
724 );
725
726 if( is_wp_error( $response ) || empty( $response['body'] ) ) {
727 $error = wpforo_phrase( "ERROR: Can't connect to Google reCAPTCHA API", false );
728 if( wpforo_setting( 'general', 'debug_mode' ) && is_wp_error( $response ) ) {
729 $error .= ' ( ' . $response->get_error_message() . ' )';
730 }
731
732 return $error;
733 }
734
735 $response_data = json_decode( $response['body'], true );
736
737 if( ! empty( $response_data['success'] ) ) {
738 // For reCAPTCHA v3, also check the score
739 if( $version === 'v3' || ( isset( $_POST['g-recaptcha-version'] ) && $_POST['g-recaptcha-version'] === 'v3' ) ) {
740 $score = isset( $response_data['score'] ) ? (float) $response_data['score'] : 0;
741 $threshold = (float) wpforo_setting( 'recaptcha', 'v3_score_threshold' );
742
743 // Default threshold to 0.5 if not set
744 if( $threshold <= 0 || $threshold > 1 ) {
745 $threshold = 0.5;
746 }
747
748 if( $score < $threshold ) {
749 // Score too low, likely a bot
750 $error = wpforo_phrase( 'reCAPTCHA verification failed: suspicious activity detected', false );
751 if( wpforo_setting( 'general', 'debug_mode' ) ) {
752 $error .= sprintf( ' (score: %.2f, threshold: %.2f)', $score, $threshold );
753 }
754
755 return $error;
756 }
757 }
758
759 return 'success';
760 } else {
761 $error = wpforo_phrase( 'Google reCAPTCHA verification failed', false );
762 if( wpforo_setting( 'general', 'debug_mode' ) && ! empty( $response_data['error-codes'] ) ) {
763 $error .= ' (' . implode( ', ', $response_data['error-codes'] ) . ')';
764 }
765
766 return $error;
767 }
768 } else {
769 return wpforo_phrase( 'Google reCAPTCHA data are not submitted', false );
770 }
771 }
772
773 public function rc_check() {
774 return wpforo_ram_get( [ &$this, '_rc_check' ] );
775 }
776
777 public function rc_verify() {
778 if( ! wpforo_setting( 'recaptcha', 'post_editor' ) || ! wpforo_setting( 'recaptcha', 'topic_editor' ) ) {
779 if( ! wpforo_setting( 'recaptcha', 'post_editor' ) && in_array(
780 wpfval( $_POST, 'wpfaction' ),
781 [ 'post_add', 'post_edit' ],
782 true
783 ) ) {
784 return true;
785 } elseif( ! wpforo_setting( 'recaptcha', 'topic_editor' ) && in_array(
786 wpfval( $_POST, 'wpfaction' ),
787 [ 'topic_add', 'topic_edit' ],
788 true
789 ) ) {
790 return true;
791 }
792 }
793 $result = $this->rc_check();
794 if( $result === 'success' ) {
795 return true;
796 } else {
797 WPF()->notice->add( $result, 'error' );
798 wp_safe_redirect( wpforo_get_request_uri() );
799 exit();
800 }
801 }
802
803 public function rc_verify_wp_login( $user ) {
804 if( ! isset( $_POST['log'] ) && ! isset( $_POST['pwd'] ) ) return $user;
805 if( ! wpforo_setting( 'recaptcha', 'login_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_login_form' ) ) {
806 if( ! wpfval( $_POST, 'wpforologin' ) && ! wpforo_setting( 'recaptcha', 'login_form' ) ) {
807 return $user;
808 } elseif( wpfval( $_POST, 'wpforologin' ) && ! wpforo_setting( 'recaptcha', 'wpf_login_form' ) ) {
809 return $user;
810 }
811 }
812 $errors = is_wp_error( $user ) ? $user : new WP_Error();
813 $result = $this->rc_check();
814 if( $result !== 'success' ) {
815 $errors->add( 'wpforo-recaptcha-error', $result );
816 $user = is_wp_error( $user ) ? $user : $errors;
817 remove_filter( 'authenticate', 'wp_authenticate_username_password' );
818 remove_filter( 'authenticate', 'wp_authenticate_cookie' );
819 }
820
821 return $user;
822 }
823
824 public function rc_verify_wp_register( $errors = '' ) {
825 if( ! is_wp_error( $errors ) ) $errors = new WP_Error();
826 if( ! wpforo_setting( 'recaptcha', 'reg_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_reg_form' ) ) {
827 if( ! wpfval( $_POST, 'wpfreg' ) && ! wpforo_setting( 'recaptcha', 'reg_form' ) ) {
828 return $errors;
829 } elseif( wpfval( $_POST, 'wpfreg' ) && ! wpforo_setting( 'recaptcha', 'wpf_reg_form' ) ) {
830 return $errors;
831 }
832 }
833 $result = $this->rc_check();
834 if( $result !== 'success' ) {
835 $errors->add( 'wpforo-recaptcha-error', $result );
836 }
837
838 return $errors;
839 }
840
841 public function rc_verify_wp_lostpassword( $errors = '' ) {
842 if( ! is_wp_error( $errors ) ) $errors = new WP_Error();
843 if( ! wpforo_setting( 'recaptcha', 'lostpass_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_lostpass_form' ) ) {
844 if( ! wpfval( $_POST, 'wpfororp' ) && ! wpforo_setting( 'recaptcha', 'lostpass_form' ) ) {
845 return;
846 } elseif( wpfval( $_POST, 'wpfororp' ) && ! wpforo_setting( 'recaptcha', 'wpf_lostpass_form' ) ) {
847 return;
848 }
849 }
850 $result = $this->rc_check();
851 if( $result !== 'success' ) {
852 if( isset( $_POST['wc_reset_password'] ) && isset( $_POST['_wp_http_referer'] ) ) {
853 //$errors->add('wpforo-recaptcha-error', $result);
854 //return $errors;
855 } else {
856 wp_die( $result, 'reCAPTCHA ERROR', [ 'back_link' => true ] );
857 }
858 }
859 }
860 }
861