PluginProbe
wpForo Forum / 3.0.8
wpForo Forum v3.0.8
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.8, at classes/API.php

863 lines 31.1 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 if( wpforo_setting( 'recaptcha', 'topic_editor' ) || wpforo_setting( 'recaptcha', 'post_editor' ) ) {
190 add_action( 'wp_enqueue_scripts', [ $this, 'rc_enqueue' ] );
191 add_action( 'wpforo_verify_form_end', [ $this, 'rc_verify' ] );
192 }
193 if( wpforo_setting( 'recaptcha', 'topic_editor' ) ) {
194 add_action( 'wpforo_topic_form_extra_fields_after', [ $this, 'rc_widget' ] );
195 }
196 if( wpforo_setting( 'recaptcha', 'post_editor' ) ) {
197 add_action( 'wpforo_reply_form_extra_fields_after', [ $this, 'rc_widget' ] );
198 add_action( 'wpforo_portable_form_extra_fields_after', [ $this, 'rc_widget' ] );
199 }
200 }
201 }
202
203 ###############################################################################
204 }
205
206 private function init_wp_recaptcha() {
207 $template = WPF()->current_object['template'];
208 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
209 $secret_key = wpforo_setting( 'recaptcha', 'secret_key' );
210
211 if( ! is_user_logged_in() && $site_key && $secret_key ) {
212 $reg_form = wpforo_setting( 'recaptcha', 'reg_form' );
213 $login_form = wpforo_setting( 'recaptcha', 'login_form' );
214 $lostpass_form = wpforo_setting( 'recaptcha', 'lostpass_form' );
215
216 //Verification Hooks: Login / Register / Reset Pass
217 if( $login_form ) add_filter( 'wp_authenticate_user', [ $this, 'rc_verify_wp_login' ], 15, 2 );
218 if( $reg_form ) add_filter( 'registration_errors', [ $this, 'rc_verify_wp_register' ], 10, 3 );
219 if( $lostpass_form ) add_action( 'lostpassword_post', [ $this, 'rc_verify_wp_lostpassword' ], 10 );
220
221 //Load reCAPTCHA API and Widget on wp-login.php
222 if( $reg_form || $login_form || $lostpass_form ) {
223 add_action( 'login_enqueue_scripts', [ $this, 'rc_enqueue' ] );
224 add_action( 'login_enqueue_scripts', [ $this, 'rc_enqueue_css' ] );
225 if( $login_form && $template !== 'login' ) add_action( 'login_form', [ $this, 'rc_widget' ] );
226 if( $reg_form && $template !== 'register' ) add_action( 'register_form', [ $this, 'rc_widget' ] );
227 if( $lostpass_form && $template !== 'lostpassword' ) {
228 add_action( 'lostpassword_form', [ $this, 'rc_widget' ] );
229 }
230 }
231 }
232 }
233
234 public function local( $api ) {
235 $wplocal = get_locale();
236 $wplocal_iso = substr( $wplocal, 0, 2 );
237
238 if( $api === 'fb' ) {
239 if( in_array( $wplocal, $this->fb_local ) ) {
240 return $wplocal;
241 } else {
242 return $this->locale;
243 }
244 } elseif( $api === 'tw' ) {
245 if( in_array( $wplocal_iso, $this->tw_local ) ) {
246 return $wplocal_iso;
247 } else {
248 return $this->locale_iso;
249 }
250 } elseif( $api === 'vk' ) {
251 return $wplocal_iso;
252 } elseif( $api === 'ok' ) {
253 if( in_array( $wplocal_iso, $this->ok_local ) ) {
254 return $wplocal_iso;
255 } else {
256 return $this->locale_iso;
257 }
258 }
259
260 return $wplocal;
261 }
262
263
264 public function fb_share_button( $url = '', $type = 'custom', $text = '' ) {
265 if( ! wpforo_setting( 'social', 'sb', 'fb' ) ) return;
266 $url = $url ?: WPF()->current_url;
267 // Facebook deprecated custom parameters (quote, title, etc.)
268 // Content is now pulled from Open Graph meta tags on the shared URL
269 $share_url = 'https://www.facebook.com/sharer/sharer.php?u=' . urlencode( (string) $url );
270 if( $type === 'custom' ) { ?>
271 <span class="wpforo-share-button wpf-fb" data-wpfurl="<?php echo esc_attr( $url ) ?>"
272 title="<?php wpforo_phrase( 'Share to Facebook' ); ?>">
273 <?php if( wpforo_setting( 'social', 'sb_icon' ) === 'figure' ): ?>
274 <i class="fab fa-facebook-f" aria-hidden="true"></i>
275 <?php elseif( wpforo_setting( 'social', 'sb_icon' ) === 'square' ): ?>
276 <i class="fab fa-facebook-square" aria-hidden="true"></i>
277 <?php else: ?>
278 <i class="fab fa-facebook" aria-hidden="true"></i>
279 <?php endif; ?>
280 </span>
281 <?php
282 } else {
283 ?>
284 <div class="wpf-sbw wpf-sbw-fb">
285 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
286 <a target="_blank" href="<?php echo esc_url( $share_url ) ?>"
287 rel="nofollow"><?php wpforo_phrase( 'Share' ); ?></a>
288 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
289 <a class="wpf-sb-button wpf-fb" href="<?php echo esc_url( $share_url ) ?>" target="_blank" rel="nofollow">
290 <i class="fab fa-facebook-f" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
291 </a>
292 <?php else: ?>
293 <a class="wpf-sb-button wpf-sb-icon wpf-fb" href="<?php echo esc_url( $share_url ) ?>" target="_blank" rel="nofollow">
294 <i class="fab fa-facebook-f" aria-hidden="true"></i>
295 </a>
296 <?php endif; ?>
297 </div>
298 <?php
299 }
300 }
301
302 public function tw_wjs() {
303 ?>
304 <script type="text/javascript">window.twttr = (function (d, s, id) {
305 var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {};
306 if (d.getElementById(id)) return t;
307 js = d.createElement(s);
308 js.id = id;
309 js.src = 'https://platform.twitter.com/widgets.js';
310 fjs.parentNode.insertBefore(js, fjs);
311 t._e = [];
312 t.ready = function (f) { t._e.push(f); };
313 return t;
314 }(document, 'script', 'twitter-wjs'));</script>
315 <?php
316 }
317
318 public function tw_share_button( $url = '', $type = 'custom', $text = '' ) {
319 if( ! wpforo_setting( 'social', 'sb', 'tw' ) ) return;
320 $url = $url ?: WPF()->current_url;
321 $n_url = strlen( (string) $url );
322 $n_text = 280 - $n_url;
323 $text = $text ?: wpfval( WPF()->current_object, 'og_text' );
324 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), $n_text, false ) );
325 if( $type == 'custom' ) { ?>
326 <a class="wpforo-share-button wpf-tw"
327 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode( (string) $url ) ?>"
328 title="<?php wpforo_phrase( 'Tweet this post' ); ?>" rel="nofollow">
329 <?php if( wpforo_setting( 'social', 'sb_icon' ) === 'figure' ): ?>
330 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
331 <?php elseif( wpforo_setting( 'social', 'sb_icon' ) === 'square' ): ?>
332 <i class="fa-brands fa-x-twitter-square" aria-hidden="true"></i>
333 <?php else: ?>
334 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
335 <?php endif; ?>
336 </a>
337 <?php
338 } else { ?>
339 <div class="wpf-sbw wpf-sbw-tw">
340 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
341 <a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button"
342 data-lang="<?php $this->local( 'tw' ) ?>" data-show-count="true" rel="nofollow"><?php wpforo_phrase(
343 'Tweet'
344 ); ?></a>
345 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
346 <a class="wpf-sb-button wpf-tw"
347 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode(
348 (string) $url
349 ) ?>" rel="nofollow">
350 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i> <span><?php wpforo_phrase(
351 'Tweet'
352 ) ?></span>
353 </a>
354 <?php else: ?>
355 <a class="wpf-sb-button wpf-sb-icon wpf-tw"
356 href="https://twitter.com/intent/tweet?text=<?php echo $text ?>&url=<?php echo urlencode(
357 (string) $url
358 ) ?>" rel="nofollow">
359 <i class="fa-brands fa-x-twitter" aria-hidden="true"></i>
360 </a>
361 <?php endif; ?>
362 </div>
363 <?php
364 }
365 }
366
367 public function vk_js() {
368 ?>
369 <script type="text/javascript" src="https://vk.com/js/api/share.js?95" charset="windows-1251"></script>
370 <?php
371 }
372
373 public function wapp_share_button( $url = '', $type = 'custom', $text = '' ) {
374 if( ! wpforo_setting( 'social', 'sb', 'wapp' ) ) return;
375 $url = $url ?: WPF()->current_url;
376 $domain = wp_is_mobile() ? 'https://api.whatsapp.com' : 'https://web.whatsapp.com';
377 $text = $text ?: ( wpfval( WPF()->current_object, 'og_text' ) ? WPF()->current_object['og_text'] : WPF()->board->get_current( 'settings' )['title'] );
378 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 100, false ) ) . ' URL: ' . urlencode( (string) $url );
379 if( $type === 'custom' ) { ?>
380 <a class="wpforo-share-button wpf-wapp" href="<?php echo $domain ?>/send?text=<?php echo $text ?>"
381 title="<?php wpforo_phrase( 'Share to WhatsApp' ); ?>" target="_blank"
382 data-action="share/whatsapp/share" rel="nofollow">
383 <i class="fab fa-whatsapp" aria-hidden="true"></i>
384 </a>
385 <?php
386 } else { ?>
387 <div class="wpf-sbw wpf-sbw-wapp">
388 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
389 <!-- WhatsApp is not available -->
390 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
391 <a class="wpf-sb-button wpf-wapp" href="<?php echo $domain ?>/send?text=<?php echo $text ?>"
392 target="_blank" data-action="share/whatsapp/share" rel="nofollow">
393 <i class="fab fa-whatsapp" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
394 </a>
395 <?php else: ?>
396 <a class="wpf-sb-button wpf-sb-icon wpf-wapp"
397 href="<?php echo $domain ?>/send?text=<?php echo $text ?>" target="_blank"
398 data-action="share/whatsapp/share" rel="nofollow">
399 <i class="fab fa-whatsapp" aria-hidden="true"></i>
400 </a>
401 <?php endif; ?>
402 </div>
403 <?php
404 }
405 }
406
407 public function lin_share_button( $url = '', $type = 'custom', $text = '' ) {
408 if( ! wpforo_setting( 'social', 'sb', 'lin' ) ) return;
409 $url = $url ?: WPF()->current_url;
410 $title = (string) wpfval( WPF()->current_object, 'topic', 'title' );
411 $text = $text ?: ( wpfval( WPF()->current_object, 'og_text' ) ? WPF()->current_object['og_text'] : WPF()->board->get_current(
412 'settings'
413 )['title'] );
414 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 500, false ) );
415 if( $type == 'custom' ) { ?>
416 <a class="wpforo-share-button wpf-lin"
417 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
418 (string) $url
419 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>"
420 title="<?php wpforo_phrase( 'Share to LinkedIn' ); ?>" target="_blank" rel="nofollow">
421 <i class="fab fa-linkedin-in" aria-hidden="true"></i>
422 </a>
423 <?php
424 } else { ?>
425 <div class="wpf-sbw wpf-sbw-lin">
426 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
427 <!-- LinkedIn is not available -->
428 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
429 <a class="wpf-sb-button wpf-lin"
430 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
431 (string) $url
432 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>" target="_blank" rel="nofollow">
433 <i class="fab fa-linkedin-in" aria-hidden="true"></i> <span><?php wpforo_phrase(
434 'Share'
435 ) ?></span>
436 </a>
437 <?php else: ?>
438 <a class="wpf-sb-button wpf-sb-icon wpf-lin"
439 href="https://www.linkedin.com/shareArticle?mini=true&url=<?php echo urlencode(
440 (string) $url
441 ) ?>&title=<?php echo urlencode( $title ) ?>&summary=<?php echo $text ?>" target="_blank" rel="nofollow">
442 <i class="fab fa-linkedin-in" aria-hidden="true"></i>
443 </a>
444 <?php endif; ?>
445 </div>
446 <?php
447 }
448 }
449
450 public function vk_share_button( $url = '', $type = 'custom', $text = '' ) {
451 if( ! wpforo_setting( 'social', 'sb', 'vk' ) ) return;
452 $url = $url ?: WPF()->current_url;
453 $text = $text ?: wpfval( WPF()->current_object, 'og_text' );
454 $text = urlencode( wpforo_text( strip_shortcodes( strip_tags( (string) $text ) ), 500, false ) );
455 if( $type === 'custom' ) { ?>
456 <a class="wpforo-share-button wpf-vk" onclick="return VK.Share.click(0, this);"
457 href="https://vk.com/share.php?url=<?php echo urlencode( (string) $url ) ?>&description=<?php echo $text ?>"
458 title="<?php wpforo_phrase( 'Share to VK' ); ?>" target="_blank" rel="nofollow">
459 <i class="fab fa-vk" aria-hidden="true"></i>
460 </a>
461 <?php
462 } else { ?>
463 <div class="wpf-sbw wpf-sbw-vk">
464 <?php if( wpforo_setting( 'social', 'sb_type' ) === 'button_count' ): ?>
465 <script type="text/javascript">document.write(VK.Share.button(false, {
466 type: 'round',
467 text: "<?php wpforo_phrase( 'Share' ); ?>",
468 }));</script>
469 <?php elseif( wpforo_setting( 'social', 'sb_type' ) === 'button' ): ?>
470 <a class="wpf-sb-button wpf-vk" onclick="return VK.Share.click(0, this);"
471 href="https://vk.com/share.php?url=<?php echo urlencode(
472 (string) $url
473 ) ?>&description=<?php echo $text ?>" target="_blank" rel="nofollow">
474 <i class="fab fa-vk" aria-hidden="true"></i> <span><?php wpforo_phrase( 'Share' ) ?></span>
475 </a>
476 <?php else: ?>
477 <a class="wpf-sb-button wpf-sb-icon wpf-vk" onclick="return VK.Share.click(0, this);"
478 href="https://vk.com/share.php?url=<?php echo urlencode(
479 (string) $url
480 ) ?>&description=<?php echo $text ?>" target="_blank" rel="nofollow">
481 <i class="fab fa-vk" aria-hidden="true"></i>
482 </a>
483 <?php endif; ?>
484 </div>
485 <?php
486 }
487 }
488
489
490 public function share_toggle( $url = '', $text = '', $type = 'custom' ) {
491 WPF()->api->fb_share_button( $url, $type );
492 WPF()->api->tw_share_button( $url, $type, $text );
493 WPF()->api->wapp_share_button( $url, $type, $text );
494 WPF()->api->lin_share_button( $url, $type, $text );
495 WPF()->api->vk_share_button( $url, $type, $text );
496 }
497
498 public function share_buttons( $url = '', $type = 'default', $text = '' ) {
499 $template = wpfval( WPF()->current_object, 'template' );
500 $exclude = [ 'lostpassword', 'resetpassword' ];
501 if( $template && ! in_array( $template, $exclude ) ) {
502 WPF()->api->fb_share_button( $url, $type );
503 WPF()->api->tw_share_button( $url, $type, $text );
504 WPF()->api->wapp_share_button( $url, $type, $text );
505 WPF()->api->lin_share_button( $url, $type, $text );
506 WPF()->api->vk_share_button( $url, $type, $text );
507 }
508 }
509
510 public function rc_enqueue() {
511 $version = wpforo_setting( 'recaptcha', 'version' );
512 $theme = wpforo_setting( 'recaptcha', 'theme' );
513 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
514
515 // reCAPTCHA v3 uses a different script URL and approach
516 if( $version === 'v3' ) {
517 wp_register_script(
518 'wpforo_recaptcha',
519 'https://www.google.com/recaptcha/api.js?render=' . $site_key
520 );
521 wp_add_inline_script(
522 'wpforo_recaptcha',
523 "var wpForoReCaptchaV3Execute = function(action){
524 return new Promise(function(resolve, reject){
525 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.execute === 'function' ){
526 grecaptcha.ready(function(){
527 grecaptcha.execute('" . esc_js( $site_key ) . "', {action: action}).then(function(token){
528 resolve(token);
529 }).catch(function(error){
530 reject(error);
531 });
532 });
533 } else {
534 reject('reCAPTCHA not loaded');
535 }
536 });
537 };
538 var wpForoReCaptchaV3Submit = function(form){
539 var btn = form.querySelector('input[type=\"submit\"], button[type=\"submit\"]');
540 if( btn && btn.name && !form.querySelector('input[type=\"hidden\"][name=\"' + btn.name + '\"]') ){
541 var h = document.createElement('input');
542 h.type = 'hidden';
543 h.name = btn.name;
544 h.value = btn.value || '1';
545 form.appendChild(h);
546 }
547 form.submit();
548 };
549 var wpForoReCaptchaV3Init = function(){
550 var forms = document.querySelectorAll('form[data-wpforo-recaptcha-v3]');
551 forms.forEach(function(form){
552 form.addEventListener('submit', function(e){
553 var tokenInput = form.querySelector('input[name=\"g-recaptcha-response\"]');
554 if( tokenInput && !tokenInput.value ){
555 e.preventDefault();
556 var action = form.getAttribute('data-wpforo-recaptcha-action') || 'wpforo_form';
557 wpForoReCaptchaV3Execute(action).then(function(token){
558 tokenInput.value = token;
559 wpForoReCaptchaV3Submit(form);
560 }).catch(function(error){
561 console.error('reCAPTCHA error:', error);
562 wpForoReCaptchaV3Submit(form);
563 });
564 }
565 });
566 });
567 };
568 if( document.readyState === 'loading' ){
569 document.addEventListener('DOMContentLoaded', wpForoReCaptchaV3Init);
570 } else {
571 wpForoReCaptchaV3Init();
572 }"
573 );
574 } elseif( $version === 'v2_invisible' ) {
575 // reCAPTCHA v2 Invisible
576 wp_register_script(
577 'wpforo_recaptcha',
578 'https://www.google.com/recaptcha/api.js?onload=wpForoReCallback&render=explicit'
579 );
580 wp_add_inline_script(
581 'wpforo_recaptcha',
582 "var wpForoReWidgetIds = {};
583 var wpForoReCallback = function(){
584 setTimeout(function () {
585 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function' ){
586 var rc_widgets = document.getElementsByClassName('wpforo_recaptcha_widget');
587 if( rc_widgets.length ){
588 var i;
589 for (i = 0; i < rc_widgets.length; i++) {
590 if( rc_widgets[i].firstElementChild === null ){
591 rc_widgets[i].innerHTML = '';
592 var form = rc_widgets[i].closest('form');
593 var widgetId = grecaptcha.render(
594 rc_widgets[i], {
595 'sitekey': '" . esc_js( $site_key ) . "',
596 'theme': '" . esc_js( $theme ) . "',
597 'size': 'invisible',
598 'callback': function(token){
599 if( form ){
600 var tokenInput = form.querySelector('input[name=\"g-recaptcha-response\"]');
601 if( !tokenInput ){
602 tokenInput = document.createElement('input');
603 tokenInput.type = 'hidden';
604 tokenInput.name = 'g-recaptcha-response';
605 form.appendChild(tokenInput);
606 }
607 tokenInput.value = token;
608 form.submit();
609 }
610 }
611 }
612 );
613 if( form ){
614 wpForoReWidgetIds[form.id || i] = widgetId;
615 form.addEventListener('submit', function(e){
616 var formId = this.id || Array.prototype.indexOf.call(document.forms, this);
617 if( wpForoReWidgetIds[formId] !== undefined ){
618 var tokenInput = this.querySelector('input[name=\"g-recaptcha-response\"]');
619 if( !tokenInput || !tokenInput.value ){
620 e.preventDefault();
621 grecaptcha.execute(wpForoReWidgetIds[formId]);
622 }
623 }
624 });
625 }
626 }
627 }
628 }
629 }
630 }, 800);
631 }"
632 );
633 } else {
634 // reCAPTCHA v2 Checkbox (default)
635 wp_register_script(
636 'wpforo_recaptcha',
637 'https://www.google.com/recaptcha/api.js?onload=wpForoReCallback&render=explicit'
638 );
639 wp_add_inline_script(
640 'wpforo_recaptcha',
641 "var wpForoReCallback = function(){
642 setTimeout(function () {
643 if( typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function' ){
644 var rc_widgets = document.getElementsByClassName('wpforo_recaptcha_widget');
645 if( rc_widgets.length ){
646 var i;
647 for (i = 0; i < rc_widgets.length; i++) {
648 if( rc_widgets[i].firstElementChild === null ){
649 rc_widgets[i].innerHTML = '';
650 grecaptcha.render(
651 rc_widgets[i], { 'sitekey': '" . esc_js( $site_key ) . "', 'theme': '" . esc_js( $theme ) . "' }
652 );
653 }
654 }
655 }
656 }
657 }, 800);
658 }"
659 );
660 }
661 wp_enqueue_script( 'wpforo_recaptcha' );
662 }
663
664 public function rc_enqueue_async( $tag, $handle ) {
665 if( $handle === 'wpforo_recaptcha' ) return str_replace( '<script', '<script async defer', $tag );
666
667 return $tag;
668 }
669
670 public function rc_enqueue_css() {
671 $version = wpforo_setting( 'recaptcha', 'version' );
672
673 // Only add CSS for v2 checkbox (visible widget)
674 // v2 invisible and v3 don't need widget styling
675 if( $version !== 'v2_checkbox' ) {
676 return;
677 }
678
679 wp_register_style( 'wpforo-rc-style', false );
680 wp_enqueue_style( 'wpforo-rc-style' );
681 $custom_css = ".wpforo_recaptcha_widget{ -webkit-transform:scale(0.9); transform:scale(0.9); -webkit-transform-origin:left 0; transform-origin:left 0; }";
682 wp_add_inline_style( 'wpforo-rc-style', $custom_css );
683 }
684
685 public function rc_widget( $action = 'wpforo_form' ) {
686 $site_key = wpforo_setting( 'recaptcha', 'site_key' );
687 $version = wpforo_setting( 'recaptcha', 'version' );
688
689 if( $site_key ) {
690 if( $version === 'v3' ) {
691 // reCAPTCHA v3 - hidden token input, form gets data attributes
692 echo '<input type="hidden" name="g-recaptcha-response" value="" />';
693 echo '<input type="hidden" name="g-recaptcha-version" value="v3" />';
694 // Add JS to mark the parent form for v3 processing
695 echo "\r\n<script>(function(){
696 var input = document.currentScript.previousElementSibling.previousElementSibling;
697 var form = input.closest('form');
698 if(form){
699 form.setAttribute('data-wpforo-recaptcha-v3', '1');
700 form.setAttribute('data-wpforo-recaptcha-action', '" . esc_js( $action ) . "');
701 }
702 })();</script>";
703 } else {
704 // reCAPTCHA v2 (checkbox or invisible) - standard widget div
705 echo '<div class="wpforo_recaptcha_widget"></div><div class="wpf-cl"></div>';
706 echo "\r\n<script>if(typeof wpForoReCallback === 'function') wpForoReCallback();</script>";
707 }
708 }
709 }
710
711 public function _rc_check() {
712 if( isset( $_POST['g-recaptcha-response'] ) && ! empty( $_POST['g-recaptcha-response'] ) ) {
713 $secret_key = wpforo_setting( 'recaptcha', 'secret_key' );
714 $version = wpforo_setting( 'recaptcha', 'version' );
715
716 // Use POST method for verification (recommended by Google)
717 $response = wp_remote_post(
718 'https://www.google.com/recaptcha/api/siteverify',
719 [
720 'body' => [
721 'secret' => $secret_key,
722 'response' => sanitize_text_field( $_POST['g-recaptcha-response'] ),
723 'remoteip' => WPF()->current_user_ip,
724 ],
725 ]
726 );
727
728 if( is_wp_error( $response ) || empty( $response['body'] ) ) {
729 $error = wpforo_phrase( "ERROR: Can't connect to Google reCAPTCHA API", false );
730 if( wpforo_setting( 'general', 'debug_mode' ) && is_wp_error( $response ) ) {
731 $error .= ' ( ' . $response->get_error_message() . ' )';
732 }
733
734 return $error;
735 }
736
737 $response_data = json_decode( $response['body'], true );
738
739 if( ! empty( $response_data['success'] ) ) {
740 // For reCAPTCHA v3, also check the score
741 if( $version === 'v3' || ( isset( $_POST['g-recaptcha-version'] ) && $_POST['g-recaptcha-version'] === 'v3' ) ) {
742 $score = isset( $response_data['score'] ) ? (float) $response_data['score'] : 0;
743 $threshold = (float) wpforo_setting( 'recaptcha', 'v3_score_threshold' );
744
745 // Default threshold to 0.5 if not set
746 if( $threshold <= 0 || $threshold > 1 ) {
747 $threshold = 0.5;
748 }
749
750 if( $score < $threshold ) {
751 // Score too low, likely a bot
752 $error = wpforo_phrase( 'reCAPTCHA verification failed: suspicious activity detected', false );
753 if( wpforo_setting( 'general', 'debug_mode' ) ) {
754 $error .= sprintf( ' (score: %.2f, threshold: %.2f)', $score, $threshold );
755 }
756
757 return $error;
758 }
759 }
760
761 return 'success';
762 } else {
763 $error = wpforo_phrase( 'Google reCAPTCHA verification failed', false );
764 if( wpforo_setting( 'general', 'debug_mode' ) && ! empty( $response_data['error-codes'] ) ) {
765 $error .= ' (' . implode( ', ', $response_data['error-codes'] ) . ')';
766 }
767
768 return $error;
769 }
770 } else {
771 return wpforo_phrase( 'Google reCAPTCHA data are not submitted', false );
772 }
773 }
774
775 public function rc_check() {
776 return wpforo_ram_get( [ &$this, '_rc_check' ] );
777 }
778
779 public function rc_verify() {
780 if( ! wpforo_setting( 'recaptcha', 'post_editor' ) || ! wpforo_setting( 'recaptcha', 'topic_editor' ) ) {
781 if( ! wpforo_setting( 'recaptcha', 'post_editor' ) && in_array(
782 wpfval( $_POST, 'wpfaction' ),
783 [ 'post_add', 'post_edit' ],
784 true
785 ) ) {
786 return true;
787 } elseif( ! wpforo_setting( 'recaptcha', 'topic_editor' ) && in_array(
788 wpfval( $_POST, 'wpfaction' ),
789 [ 'topic_add', 'topic_edit' ],
790 true
791 ) ) {
792 return true;
793 }
794 }
795 $result = $this->rc_check();
796 if( $result === 'success' ) {
797 return true;
798 } else {
799 WPF()->notice->add( $result, 'error' );
800 wp_safe_redirect( wpforo_get_request_uri() );
801 exit();
802 }
803 }
804
805 public function rc_verify_wp_login( $user ) {
806 if( ! isset( $_POST['log'] ) && ! isset( $_POST['pwd'] ) ) return $user;
807 if( ! wpforo_setting( 'recaptcha', 'login_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_login_form' ) ) {
808 if( ! wpfval( $_POST, 'wpforologin' ) && ! wpforo_setting( 'recaptcha', 'login_form' ) ) {
809 return $user;
810 } elseif( wpfval( $_POST, 'wpforologin' ) && ! wpforo_setting( 'recaptcha', 'wpf_login_form' ) ) {
811 return $user;
812 }
813 }
814 $errors = is_wp_error( $user ) ? $user : new WP_Error();
815 $result = $this->rc_check();
816 if( $result !== 'success' ) {
817 $errors->add( 'wpforo-recaptcha-error', $result );
818 $user = is_wp_error( $user ) ? $user : $errors;
819 remove_filter( 'authenticate', 'wp_authenticate_username_password' );
820 remove_filter( 'authenticate', 'wp_authenticate_cookie' );
821 }
822
823 return $user;
824 }
825
826 public function rc_verify_wp_register( $errors = '' ) {
827 if( ! is_wp_error( $errors ) ) $errors = new WP_Error();
828 if( ! wpforo_setting( 'recaptcha', 'reg_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_reg_form' ) ) {
829 if( ! wpfval( $_POST, 'wpfreg' ) && ! wpforo_setting( 'recaptcha', 'reg_form' ) ) {
830 return $errors;
831 } elseif( wpfval( $_POST, 'wpfreg' ) && ! wpforo_setting( 'recaptcha', 'wpf_reg_form' ) ) {
832 return $errors;
833 }
834 }
835 $result = $this->rc_check();
836 if( $result !== 'success' ) {
837 $errors->add( 'wpforo-recaptcha-error', $result );
838 }
839
840 return $errors;
841 }
842
843 public function rc_verify_wp_lostpassword( $errors = '' ) {
844 if( ! is_wp_error( $errors ) ) $errors = new WP_Error();
845 if( ! wpforo_setting( 'recaptcha', 'lostpass_form' ) || ! wpforo_setting( 'recaptcha', 'wpf_lostpass_form' ) ) {
846 if( ! wpfval( $_POST, 'wpfororp' ) && ! wpforo_setting( 'recaptcha', 'lostpass_form' ) ) {
847 return;
848 } elseif( wpfval( $_POST, 'wpfororp' ) && ! wpforo_setting( 'recaptcha', 'wpf_lostpass_form' ) ) {
849 return;
850 }
851 }
852 $result = $this->rc_check();
853 if( $result !== 'success' ) {
854 if( isset( $_POST['wc_reset_password'] ) && isset( $_POST['_wp_http_referer'] ) ) {
855 //$errors->add('wpforo-recaptcha-error', $result);
856 //return $errors;
857 } else {
858 wp_die( $result, 'reCAPTCHA ERROR', [ 'back_link' => true ] );
859 }
860 }
861 }
862 }
863