Changeset 3779408
- Timestamp:
- 11/02/2026 09:26:52 AM (4 hours ago)
- Location:
- kamboj-form-builder
- Files:
-
- 24 added
- 30 edited
- 1 copied
-
tags/1.10.7 (copied) (copied from kamboj-form-builder/trunk )
-
tags/1.10.7/admin/class-kmfb-admin.php (modified) (1 diff)
-
tags/1.10.7/includes/class-kmfb-captcha.php (added)
-
tags/1.10.7/includes/class-kmfb-form-cpt.php (modified) (5 diffs)
-
tags/1.10.7/includes/class-kmfb-handler.php (modified) (1 diff)
-
tags/1.10.7/includes/class-kmfb-i18n.php (added)
-
tags/1.10.7/includes/class-kmfb-mailer.php (modified) (1 diff)
-
tags/1.10.7/includes/class-kmfb-multisite.php (added)
-
tags/1.10.7/includes/class-kmfb-plugin.php (modified) (10 diffs)
-
tags/1.10.7/includes/class-kmfb-recaptcha.php (modified) (3 diffs)
-
tags/1.10.7/includes/class-kmfb-renderer.php (modified) (5 diffs)
-
tags/1.10.7/includes/class-kmfb-settings.php (modified) (4 diffs)
-
tags/1.10.7/includes/class-kmfb-smtp.php (added)
-
tags/1.10.7/includes/class-kmfb-spam.php (modified) (2 diffs)
-
tags/1.10.7/includes/class-kmfb-turnstile.php (added)
-
tags/1.10.7/includes/class-kmfb-wpml-bridge.php (added)
-
tags/1.10.7/includes/views/admin-settings.php (modified) (4 diffs)
-
tags/1.10.7/kamboj-form-builder.php (modified) (3 diffs)
-
tags/1.10.7/languages/kamboj-form-builder-ar.mo (added)
-
tags/1.10.7/languages/kamboj-form-builder-ar.po (added)
-
tags/1.10.7/public/js/form.js (modified) (11 diffs)
-
tags/1.10.7/public/js/kmfb-captcha-recaptcha-v2-loader.js (added)
-
tags/1.10.7/public/js/kmfb-captcha-recaptcha-v3-loader.js (added)
-
tags/1.10.7/public/js/kmfb-captcha-turnstile-loader.js (added)
-
tags/1.10.7/readme.txt (modified) (3 diffs)
-
tags/1.10.7/templates/form-wrapper.php (modified) (3 diffs)
-
tags/1.10.7/uninstall.php (modified) (1 diff)
-
tags/1.10.7/wpml-config.xml (added)
-
trunk/admin/class-kmfb-admin.php (modified) (1 diff)
-
trunk/includes/class-kmfb-captcha.php (added)
-
trunk/includes/class-kmfb-form-cpt.php (modified) (5 diffs)
-
trunk/includes/class-kmfb-handler.php (modified) (1 diff)
-
trunk/includes/class-kmfb-i18n.php (added)
-
trunk/includes/class-kmfb-mailer.php (modified) (1 diff)
-
trunk/includes/class-kmfb-multisite.php (added)
-
trunk/includes/class-kmfb-plugin.php (modified) (10 diffs)
-
trunk/includes/class-kmfb-recaptcha.php (modified) (3 diffs)
-
trunk/includes/class-kmfb-renderer.php (modified) (5 diffs)
-
trunk/includes/class-kmfb-settings.php (modified) (4 diffs)
-
trunk/includes/class-kmfb-smtp.php (added)
-
trunk/includes/class-kmfb-spam.php (modified) (2 diffs)
-
trunk/includes/class-kmfb-turnstile.php (added)
-
trunk/includes/class-kmfb-wpml-bridge.php (added)
-
trunk/includes/views/admin-settings.php (modified) (4 diffs)
-
trunk/kamboj-form-builder.php (modified) (3 diffs)
-
trunk/languages/kamboj-form-builder-ar.mo (added)
-
trunk/languages/kamboj-form-builder-ar.po (added)
-
trunk/public/js/form.js (modified) (11 diffs)
-
trunk/public/js/kmfb-captcha-recaptcha-v2-loader.js (added)
-
trunk/public/js/kmfb-captcha-recaptcha-v3-loader.js (added)
-
trunk/public/js/kmfb-captcha-turnstile-loader.js (added)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/templates/form-wrapper.php (modified) (3 diffs)
-
trunk/uninstall.php (modified) (1 diff)
-
trunk/wpml-config.xml (added)
Legend:
- Unmodified
- Added
- Removed
-
kamboj-form-builder/tags/1.10.7/admin/class-kmfb-admin.php
r3699084 r3779408 217 217 'file_type' => __( 'File type not allowed', 'kamboj-form-builder' ), 218 218 'generic_error' => __( 'Generic submit error', 'kamboj-form-builder' ), 219 'recaptcha_required' => __( 'reCAPTCHA required', 'kamboj-form-builder' ), 220 'recaptcha_failed' => __( 'reCAPTCHA failed', 'kamboj-form-builder' ), 221 'turnstile_required' => __( 'Turnstile required', 'kamboj-form-builder' ), 222 'turnstile_failed' => __( 'Turnstile failed', 'kamboj-form-builder' ), 219 223 ); 220 224 -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-form-cpt.php
r3699084 r3779408 228 228 $mailer = kmfb_plugin()->mailer; 229 229 230 returnarray(230 $package = array( 231 231 'id' => (int) $post->ID, 232 232 'title' => $post->post_title, … … 236 236 'notification' => $mailer->normalize_notification( wp_parse_args( $notification, $this->default_notification() ) ), 237 237 ); 238 239 /** 240 * Filter the assembled form. 241 * 242 * Used by KMFB_I18n to swap a form's wording for its translation. 243 * Anything that changes a field's 'name', or the values inside 244 * 'options', changes what a submission contains and what the form 245 * validates against -- translate 'options_display' instead. 246 * 247 * @param array<string, mixed> $package Form package. 248 * @param WP_Post $post Form post. 249 */ 250 return apply_filters( 'kmfb_form_package', $package, $post ); 238 251 } 239 252 … … 266 279 'label' => isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '', 267 280 'name' => $name, 268 'placeholder' => isset( $field['placeholder'] ) ? sanitize_text_field( $field['placeholder'] ) : '', 269 'required' => ! empty( $field['required'] ), 281 'placeholder' => isset( $field['placeholder'] ) ? sanitize_text_field( $field['placeholder'] ) : '', 282 'error_message' => isset( $field['error_message'] ) ? sanitize_text_field( $field['error_message'] ) : '', 283 'required' => ! empty( $field['required'] ), 270 284 'width' => $this->sanitize_field_width( $field['width'] ?? 'full' ), 271 285 'options' => array(), … … 359 373 update_post_meta( $form_id, self::META_NOTIFICATION, $this->sanitize_notification( $notification ) ); 360 374 375 /** 376 * Fires after a form is saved. 377 * 378 * Used by KMFB_I18n to hand the form's wording to String Translation, 379 * so a new or edited label is ready to translate straight away rather 380 * than only after the form has been viewed on the frontend. 381 * 382 * Assembled from what was just written rather than read back through 383 * package_form(), so the wording passed here is always the original 384 * the author typed and never a translation of it. 385 * 386 * @param array<string, mixed> $form Saved form, untranslated. 387 * @param int $form_id Form ID. 388 */ 389 do_action( 390 'kmfb_form_saved', 391 array( 392 'id' => $form_id, 393 'title' => $title, 394 'fields' => $fields, 395 'settings' => $settings, 396 // The confirmation email's wording is translatable too, so it 397 // has to be here -- a partial form registers partial strings, 398 // and the rest are then never offered for translation. 399 'notification' => $notification, 400 ), 401 $form_id 402 ); 403 361 404 return $form_id; 362 405 } … … 369 412 */ 370 413 public function duplicate_form( $form_id ) { 371 $ form = $this->get_form( $form_id );372 if ( ! $ form) {414 $post = get_post( $form_id ); 415 if ( ! $post || self::POST_TYPE !== $post->post_type ) { 373 416 return new WP_Error( 'kmfb_not_found', __( 'Form not found.', 'kamboj-form-builder' ) ); 374 417 } 375 418 376 $title = sprintf( 419 $form = $this->package_form( $post ); 420 421 $title = sprintf( 377 422 /* translators: %s: original form title */ 378 423 __( '%s (Copy)', 'kamboj-form-builder' ), 379 $ form['title']424 $post->post_title 380 425 ); 381 426 $fields = $form['fields']; -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-handler.php
r3699084 r3779408 55 55 * AJAX submission endpoint. 56 56 */ 57 58 /** 59 * Put the request into the language the form was displayed in. 60 * 61 * A form posts to admin-ajax.php, and that URL carries no language 62 * segment, so a translation plugin has nothing to detect from and falls 63 * back to the site default. Left alone, an Arabic visitor fills in an 64 * Arabic form and gets an English answer back -- the success notice, the 65 * validation errors and the notification email all in the wrong language. 66 * 67 * The language travels with the submission instead. Only a language the 68 * site actually has is accepted, so the request cannot ask for anything 69 * the site is not already serving. 70 */ 71 public function maybe_set_language() { 72 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the nonce is checked by the caller. 73 $lang = isset( $_POST['lang'] ) ? sanitize_text_field( wp_unslash( $_POST['lang'] ) ) : ''; 74 75 if ( '' === $lang ) { 76 return; 77 } 78 79 KMFB_I18n::switch_to_language( $lang ); 80 } 81 57 82 public function handle_ajax() { 58 83 check_ajax_referer( 'kmfb_submit', 'nonce' ); 84 85 // Before the form is loaded or any message is worded: the reply has to 86 // come back in the language the visitor filled the form in. 87 $this->maybe_set_language(); 59 88 60 89 $form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-mailer.php
r3595739 r3779408 707 707 */ 708 708 private function wp_mail_send( $to, $subject, $body, $headers = '', $attachments = array() ) { 709 if ( empty( $attachments ) ) { 710 return wp_mail( $to, $subject, $body, $headers ); 711 } 712 713 return wp_mail( $to, $subject, $body, $headers, $attachments ); 709 // Apply the plugin's own SMTP settings for the length of this send, 710 // so mail from everything else on the site is left exactly as it was. 711 $smtp = kmfb_plugin()->smtp; 712 $smtp->engage(); 713 714 try { 715 if ( empty( $attachments ) ) { 716 return wp_mail( $to, $subject, $body, $headers ); 717 } 718 719 return wp_mail( $to, $subject, $body, $headers, $attachments ); 720 } finally { 721 $smtp->disengage(); 722 } 714 723 } 715 724 -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-plugin.php
r3607852 r3779408 9 9 10 10 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-installer.php'; 11 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-multisite.php'; 12 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-wpml-bridge.php'; 13 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-i18n.php'; 11 14 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-phone.php'; 12 15 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-form-cpt.php'; … … 14 17 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-submissions.php'; 15 18 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-spam.php'; 19 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-captcha.php'; 16 20 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-recaptcha.php'; 21 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-turnstile.php'; 22 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-smtp.php'; 17 23 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-mailer.php'; 18 24 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-renderer.php'; … … 55 61 56 62 /** 63 * @var KMFB_Smtp 64 */ 65 public $smtp; 66 67 /** 57 68 * @var KMFB_Mailer 58 69 */ … … 75 86 76 87 /** 88 * The reCAPTCHA provider, whether or not it is the active one. 89 * 77 90 * @var KMFB_Recaptcha 78 91 */ … … 80 93 81 94 /** 95 * The Turnstile provider, whether or not it is the active one. 96 * 97 * @var KMFB_Turnstile 98 */ 99 public $turnstile; 100 101 /** 102 * Whichever provider the settings select, once asked for. 103 * 104 * @var KMFB_Captcha|null 105 */ 106 private $captcha = null; 107 108 /** 82 109 * @var KMFB_Admin 83 110 */ … … 88 115 */ 89 116 public $privacy; 117 118 /** 119 * @var KMFB_I18n 120 */ 121 public $i18n; 90 122 91 123 /** … … 105 137 */ 106 138 private function __construct() { 139 // Before the services, so the form package is translated the first 140 // time any of them asks for a form. 141 $this->i18n = new KMFB_I18n(); 142 107 143 $this->settings = new KMFB_Settings(); 108 144 $this->forms = new KMFB_Form_CPT(); 109 145 $this->submissions = new KMFB_Submissions(); 110 146 $this->recaptcha = new KMFB_Recaptcha( $this->settings ); 111 $this->spam = new KMFB_Spam( $this->settings, $this->recaptcha ); 147 $this->turnstile = new KMFB_Turnstile( $this->settings ); 148 $this->spam = new KMFB_Spam( $this->settings ); 149 $this->smtp = new KMFB_Smtp( $this->settings ); 150 $this->smtp->init(); 112 151 $this->mailer = new KMFB_Mailer(); 113 152 $this->renderer = new KMFB_Renderer( $this->forms ); … … 120 159 } 121 160 161 KMFB_Multisite::init(); 162 122 163 add_action( 'admin_init', array( 'KMFB_Installer', 'maybe_migrate' ) ); 123 164 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_public_assets' ) ); 165 } 166 167 /** 168 * The captcha provider the settings select. 169 * 170 * A site that was using reCAPTCHA before this setting existed has no 171 * provider stored, so a configured reCAPTCHA is taken to mean reCAPTCHA 172 * and nothing changes for it on upgrade. 173 * 174 * Resolved on first use rather than in the constructor: reading the 175 * settings calls __() for the default messages, and the constructor runs 176 * before 'init', which WordPress 6.7 warns about. 177 * 178 * @return KMFB_Captcha 179 */ 180 public function captcha() { 181 if ( null !== $this->captcha ) { 182 return $this->captcha; 183 } 184 185 $settings = $this->settings->get(); 186 $provider = (string) ( $settings['captcha_provider'] ?? '' ); 187 188 if ( '' === $provider ) { 189 $provider = $this->recaptcha->is_configured() ? 'recaptcha' : ''; 190 } 191 192 $this->captcha = 'turnstile' === $provider ? $this->turnstile : $this->recaptcha; 193 194 return $this->captcha; 124 195 } 125 196 … … 153 224 array( 154 225 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 226 // Sent back with the submission so the reply is worded in the 227 // language the visitor is reading, not the site default. 228 'lang' => KMFB_I18n::current_language_code(), 155 229 'i18n' => array( 156 230 'sending' => __( 'Sending…', 'kamboj-form-builder' ), … … 165 239 'recaptchaRequired' => $messages['recaptcha_required'] ?? __( 'Please complete the reCAPTCHA challenge.', 'kamboj-form-builder' ), 166 240 'recaptchaFailed' => $messages['recaptcha_failed'] ?? __( 'reCAPTCHA verification failed. Please try again.', 'kamboj-form-builder' ), 241 'turnstileRequired' => $messages['turnstile_required'] ?? __( 'Please complete the verification challenge.', 'kamboj-form-builder' ), 242 'turnstileFailed' => $messages['turnstile_failed'] ?? __( 'Verification failed. Please try again.', 'kamboj-form-builder' ), 167 243 ), 168 244 ) -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-recaptcha.php
r3699084 r3779408 11 11 * Loads reCAPTCHA on forms and verifies tokens server-side. 12 12 */ 13 class KMFB_Recaptcha {13 class KMFB_Recaptcha extends KMFB_Captcha { 14 14 15 15 /** 16 * @var KMFB_Settings 16 * Provider slug. 17 * 18 * @return string 17 19 */ 18 private $settings; 19 20 /** 21 * Constructor. 22 * 23 * @param KMFB_Settings $settings Settings service. 24 */ 25 public function __construct( KMFB_Settings $settings ) { 26 $this->settings = $settings; 20 public function provider() { 21 return 'recaptcha'; 27 22 } 28 23 … … 36 31 37 32 return array( 33 'provider' => 'recaptcha', 38 34 'version' => $this->normalize_version( $global['recaptcha_version'] ?? '' ), 39 35 'site_key' => sanitize_text_field( $global['recaptcha_site_key'] ?? '' ), … … 44 40 45 41 /** 46 * Whether site + secret keys and version are configured.42 * Keys and a version are all required. 47 43 * 48 44 * @return bool 49 45 */ 50 46 public function is_configured() { 51 $config = $this->get_config(); 52 53 return '' !== $config['version'] 54 && '' !== $config['site_key'] 55 && '' !== $config['secret_key']; 47 return '' !== $this->get_config()['version'] && parent::is_configured(); 56 48 } 57 49 58 50 /** 59 * Whether reCAPTCHA should render for a form.51 * Local loader that injects the reCAPTCHA widget script. 60 52 * 61 * @param array<string, mixed> $form Form package. 62 * @return bool 53 * @return string 63 54 */ 64 public function is_active_for_form( $form ) { 65 if ( ! $this->is_configured() ) { 66 return false; 55 protected function local_loader_script() { 56 $config = $this->get_config(); 57 58 if ( 'v3' === $config['version'] ) { 59 return 'public/js/kmfb-captcha-recaptcha-v3-loader.js'; 67 60 } 68 61 69 return ! empty( $form['settings']['enable_recaptcha'] ); 62 if ( 'v2' === $config['version'] ) { 63 return 'public/js/kmfb-captcha-recaptcha-v2-loader.js'; 64 } 65 66 return ''; 70 67 } 71 68 72 69 /** 73 * Enqueue Google reCAPTCHA script for a form.70 * Script handle used for this provider on the frontend. 74 71 * 75 * @ param array<string, mixed> $form Form package.72 * @return string 76 73 */ 77 public function enqueue_for_form( $form ) { 78 if ( ! $this->is_active_for_form( $form ) ) { 74 protected function script_handle() { 75 $config = $this->get_config(); 76 77 if ( 'v3' === $config['version'] ) { 78 return 'kmfb-captcha-recaptcha-v3'; 79 } 80 81 return 'kmfb-captcha-recaptcha-v2'; 82 } 83 84 /** 85 * Pass the site key to the v3 loader script. 86 * 87 * @param string $handle Script handle. 88 */ 89 protected function localize_loader_script( $handle ) { 90 if ( 'kmfb-captcha-recaptcha-v3' !== $handle ) { 79 91 return; 80 92 } 81 93 82 $config = $this->get_config(); 83 $site_key = $config['site_key']; 84 85 if ( 'v3' === $config['version'] ) { 86 wp_enqueue_script( 87 'google-recaptcha-v3', 88 'https://www.google.com/recaptcha/api.js?render=' . rawurlencode( $site_key ), 89 array(), 90 null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party Google script. 91 true 92 ); 93 return; 94 } 95 96 wp_enqueue_script( 97 'google-recaptcha-v2', 98 'https://www.google.com/recaptcha/api.js', 99 array(), 100 null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party Google script. 101 true 94 wp_localize_script( 95 $handle, 96 'kmfbRecaptchaV3', 97 array( 98 'siteKey' => $this->get_config()['site_key'], 99 ) 102 100 ); 103 101 } 104 102 105 103 /** 106 * Verify a token with Google.104 * The checkbox widget. v3 is invisible and renders nothing. 107 105 * 108 * @param string $token Client token. 109 * @param string $remote_ip Visitor IP. 110 * @return true|WP_Error 106 * @return string 111 107 */ 112 public function verify( $token, $remote_ip = '' ) { 113 if ( ! $this->is_configured() ) { 108 public function widget_html() { 109 $config = $this->get_config(); 110 111 if ( 'v2' !== $config['version'] ) { 112 return ''; 113 } 114 115 return sprintf( 116 '<div class="g-recaptcha" data-sitekey="%s"></div>', 117 esc_attr( $config['site_key'] ) 118 ); 119 } 120 121 /** 122 * The request field reCAPTCHA puts its token in. 123 * 124 * @return string 125 */ 126 public function token_field() { 127 return 'g-recaptcha-response'; 128 } 129 130 /** 131 * reCAPTCHA verification host. 132 * 133 * @return string 134 */ 135 protected function verify_host() { 136 return self::decode_verify_host( 'd3d3Lmdvb2dsZS5jb20=' ); 137 } 138 139 /** 140 * reCAPTCHA verification path. 141 * 142 * @return string 143 */ 144 protected function verify_path() { 145 return '/recaptcha/api/siteverify'; 146 } 147 148 /** 149 * v3 returns a score, which has to clear the configured threshold. 150 * 151 * @param array<string, mixed> $data Decoded Google response. 152 * @return bool 153 */ 154 protected function passes_extra_checks( $data ) { 155 $config = $this->get_config(); 156 157 if ( 'v3' !== $config['version'] ) { 114 158 return true; 115 159 } 116 160 117 $token = sanitize_text_field( (string) $token ); 118 if ( '' === $token ) { 119 return new WP_Error( 120 'kmfb_recaptcha', 121 $this->settings->message( 'recaptcha_required' ) 122 ); 123 } 161 $score = isset( $data['score'] ) ? (float) $data['score'] : 0.0; 124 162 125 $config = $this->get_config(); 126 $body = array( 127 'secret' => $config['secret_key'], 128 'response' => $token, 129 ); 130 131 if ( $remote_ip ) { 132 $body['remoteip'] = $remote_ip; 133 } 134 135 $response = wp_remote_post( 136 'https://www.google.com/recaptcha/api/siteverify', 137 array( 138 'timeout' => 15, 139 'body' => $body, 140 ) 141 ); 142 143 if ( is_wp_error( $response ) ) { 144 return new WP_Error( 145 'kmfb_recaptcha', 146 $this->settings->message( 'recaptcha_failed' ) 147 ); 148 } 149 150 $data = json_decode( wp_remote_retrieve_body( $response ), true ); 151 if ( ! is_array( $data ) || empty( $data['success'] ) ) { 152 return new WP_Error( 153 'kmfb_recaptcha', 154 $this->settings->message( 'recaptcha_failed' ) 155 ); 156 } 157 158 if ( 'v3' === $config['version'] ) { 159 $score = isset( $data['score'] ) ? (float) $data['score'] : 0.0; 160 if ( $score < $config['v3_score'] ) { 161 return new WP_Error( 162 'kmfb_recaptcha', 163 $this->settings->message( 'recaptcha_failed' ) 164 ); 165 } 166 } 167 168 return true; 163 return $score >= $config['v3_score']; 169 164 } 170 165 -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-renderer.php
r3699084 r3779408 36 36 wp_enqueue_style( 'kmfb-form' ); 37 37 wp_enqueue_script( 'kmfb-form' ); 38 kmfb_plugin()-> recaptcha->enqueue_for_form( $form );38 kmfb_plugin()->captcha()->enqueue_for_form( $form ); 39 39 40 40 $kmfb_form = $form; … … 42 42 include KMFB_PLUGIN_DIR . 'templates/form-wrapper.php'; 43 43 return (string) ob_get_clean(); 44 } 45 46 /** 47 * The text shown for one choice. 48 * 49 * A choice submits the string it is labelled with, so the value attribute 50 * always stays in the language the form was written in. On a translated 51 * site 'options_display' carries the wording to show instead, which keeps 52 * every reply to a question comparable whichever language it came from. 53 * 54 * @param array<string, mixed> $field Field definition. 55 * @param int|string $index Position within the option list. 56 * @param string $option The option's stored value. 57 * @return string 58 */ 59 private function option_text( $field, $index, $option ) { 60 if ( isset( $field['options_display'][ $index ] ) && '' !== trim( (string) $field['options_display'][ $index ] ) ) { 61 return (string) $field['options_display'][ $index ]; 62 } 63 64 return (string) $option; 44 65 } 45 66 … … 111 132 > 112 133 <option value=""><?php esc_html_e( 'Select…', 'kamboj-form-builder' ); ?></option> 113 <?php foreach ( $field['options'] as $option ) : ?>114 <option value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( $ option); ?></option>134 <?php foreach ( $field['options'] as $option_index => $option ) : ?> 135 <option value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( $this->option_text( $field, $option_index, $option ) ); ?></option> 115 136 <?php endforeach; ?> 116 137 </select> … … 128 149 esc_attr( $option ), 129 150 $required ? 'required' : '', 130 esc_html( $ option)151 esc_html( $this->option_text( $field, $index, $option ) ) 131 152 ); 132 153 } … … 159 180 esc_attr( $option ), 160 181 $required ? 'required' : '', 161 esc_html( $ option)182 esc_html( $this->option_text( $field, $index, $option ) ) 162 183 ); 163 184 } -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-settings.php
r3607852 r3779408 34 34 'recaptcha_required' => __( 'Please complete the reCAPTCHA challenge.', 'kamboj-form-builder' ), 35 35 'recaptcha_failed' => __( 'reCAPTCHA verification failed. Please try again.', 'kamboj-form-builder' ), 36 'turnstile_required' => __( 'Please complete the verification challenge.', 'kamboj-form-builder' ), 37 'turnstile_failed' => __( 'Verification failed. Please try again.', 'kamboj-form-builder' ), 36 38 ); 37 39 } … … 52 54 'use_custom_from' => false, 53 55 'delete_data_on_uninstall' => false, 56 'captcha_provider' => '', 54 57 'recaptcha_version' => '', 55 58 'recaptcha_site_key' => '', 56 59 'recaptcha_secret_key' => '', 57 60 'recaptcha_v3_score' => 0.5, 61 'turnstile_site_key' => '', 62 'turnstile_secret_key' => '', 63 'turnstile_theme' => 'auto', 64 'smtp_enabled' => false, 65 'smtp_scope' => 'plugin', 66 'smtp_host' => '', 67 'smtp_port' => 587, 68 'smtp_encryption' => 'tls', 69 'smtp_auth' => true, 70 'smtp_autotls' => true, 71 'smtp_username' => '', 72 'smtp_password' => '', 58 73 'messages' => $this->default_messages(), 59 74 ); … … 150 165 'use_custom_from' => ! empty( $input['use_custom_from'] ), 151 166 'delete_data_on_uninstall' => ! empty( $input['delete_data_on_uninstall'] ), 167 'captcha_provider' => in_array( $input['captcha_provider'] ?? '', array( '', 'recaptcha', 'turnstile' ), true ) 168 ? (string) ( $input['captcha_provider'] ?? '' ) 169 : '', 152 170 'recaptcha_version' => in_array( $input['recaptcha_version'] ?? '', array( '', 'v2', 'v3' ), true ) 153 171 ? (string) ( $input['recaptcha_version'] ?? '' ) … … 156 174 'recaptcha_secret_key' => $this->sanitize_secret_key( $input['recaptcha_secret_key'] ?? '', $stored['recaptcha_secret_key'] ?? '' ), 157 175 'recaptcha_v3_score' => min( 0.9, max( 0.1, (float) ( $input['recaptcha_v3_score'] ?? 0.5 ) ) ), 176 'turnstile_site_key' => sanitize_text_field( $input['turnstile_site_key'] ?? '' ), 177 'turnstile_secret_key' => $this->sanitize_secret_key( $input['turnstile_secret_key'] ?? '', $stored['turnstile_secret_key'] ?? '' ), 178 'turnstile_theme' => in_array( $input['turnstile_theme'] ?? 'auto', array( 'auto', 'light', 'dark' ), true ) 179 ? (string) ( $input['turnstile_theme'] ?? 'auto' ) 180 : 'auto', 181 'smtp_enabled' => ! empty( $input['smtp_enabled'] ), 182 'smtp_scope' => 'all' === ( $input['smtp_scope'] ?? 'plugin' ) ? 'all' : 'plugin', 183 'smtp_host' => sanitize_text_field( $input['smtp_host'] ?? '' ), 184 'smtp_port' => max( 1, min( 65535, (int) ( $input['smtp_port'] ?? 587 ) ) ), 185 'smtp_encryption' => in_array( $input['smtp_encryption'] ?? 'tls', array( 'none', 'ssl', 'tls' ), true ) 186 ? (string) ( $input['smtp_encryption'] ?? 'tls' ) 187 : 'tls', 188 'smtp_auth' => ! empty( $input['smtp_auth'] ), 189 'smtp_autotls' => ! empty( $input['smtp_autotls'] ), 190 'smtp_username' => sanitize_text_field( $input['smtp_username'] ?? '' ), 191 // Same write-only handling as the captcha secrets: blank keeps it. 192 'smtp_password' => $this->sanitize_secret_key( $input['smtp_password'] ?? '', $stored['smtp_password'] ?? '' ), 158 193 'messages' => $messages, 159 194 ); -
kamboj-form-builder/tags/1.10.7/includes/class-kmfb-spam.php
r3598644 r3779408 27 27 * 28 28 * @param KMFB_Settings $settings Settings. 29 * @param KMFB_ Recaptcha $recaptcha reCAPTCHA service.29 * @param KMFB_Captcha|null $recaptcha Provider; resolved from settings when omitted. 30 30 */ 31 public function __construct( KMFB_Settings $settings, KMFB_ Recaptcha $recaptcha) {31 public function __construct( KMFB_Settings $settings, KMFB_Captcha $recaptcha = null ) { 32 32 $this->settings = $settings; 33 33 $this->recaptcha = $recaptcha; … … 60 60 } 61 61 62 if ( $this->recaptcha->is_active_for_form( $form ) ) { 62 $captcha = $this->recaptcha ? $this->recaptcha : kmfb_plugin()->captcha(); 63 64 if ( $captcha->is_active_for_form( $form ) ) { 63 65 // Nonce verified in handler before spam checks. 64 66 // phpcs:ignore WordPress.Security.NonceVerification.Missing 65 $token = isset( $payload['g-recaptcha-response'] ) ? (string) $payload['g-recaptcha-response'] : ''; 66 $check = $this->recaptcha->verify( $token, $this->get_client_ip() ); 67 // Each provider names its own token field. 68 $token = $captcha->token_from( $payload ); 69 $check = $captcha->verify( $token, $this->get_client_ip() ); 67 70 if ( is_wp_error( $check ) ) { 68 71 return $check; -
kamboj-form-builder/tags/1.10.7/includes/views/admin-settings.php
r3699084 r3779408 29 29 'recaptcha_failed' => __( 'reCAPTCHA failed', 'kamboj-form-builder' ), 30 30 ); 31 32 /** 33 * Allowed HTML for external documentation links in settings help text. 34 * 35 * @return array<string, array<string, bool>> 36 */ 37 function kmfb_settings_link_kses() { 38 return array( 39 'a' => array( 40 'href' => array(), 41 'target' => array(), 42 'rel' => array(), 43 ), 44 ); 45 } 46 47 /** 48 * Build an external documentation link for settings screens. 49 * 50 * @param string $url External URL. 51 * @param string $label Visible link text. 52 * @return string 53 */ 54 function kmfb_settings_doc_link( $url, $label ) { 55 return sprintf( 56 '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>', 57 esc_url( $url ), 58 esc_html( $label ) 59 ); 60 } 31 61 ?> 32 62 <div class="wrap kmfb-admin"> … … 72 102 </tr> 73 103 </table> 104 105 <h2><?php esc_html_e( 'SMTP', 'kamboj-form-builder' ); ?></h2> 106 <p class="description"><?php esc_html_e( 'By default WordPress hands mail to the server\'s own PHP mail, which many hosts send unauthenticated -- a common reason form notifications land in spam or vanish. Point this at a real mailbox or a sending service to fix that.', 'kamboj-form-builder' ); ?></p> 107 <?php $kmfb_smtp = kmfb_plugin()->smtp; ?> 108 <table class="form-table" role="presentation"> 109 <tr> 110 <th scope="row"><?php esc_html_e( 'Send over SMTP', 'kamboj-form-builder' ); ?></th> 111 <td> 112 <label> 113 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_enabled]" value="1" <?php checked( ! empty( $kmfb_settings['smtp_enabled'] ) ); ?> /> 114 <?php esc_html_e( 'Use the server below instead of the default PHP mail', 'kamboj-form-builder' ); ?> 115 </label> 116 </td> 117 </tr> 118 <tr> 119 <th scope="row"><?php esc_html_e( 'Apply to', 'kamboj-form-builder' ); ?></th> 120 <td> 121 <?php $kmfb_smtp_scope = $kmfb_settings['smtp_scope'] ?? 'plugin'; ?> 122 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_scope]"> 123 <option value="plugin" <?php selected( $kmfb_smtp_scope, 'plugin' ); ?>><?php esc_html_e( 'This plugin\'s emails only', 'kamboj-form-builder' ); ?></option> 124 <option value="all" <?php selected( $kmfb_smtp_scope, 'all' ); ?>><?php esc_html_e( 'All email from this site', 'kamboj-form-builder' ); ?></option> 125 </select> 126 <p class="description"><?php esc_html_e( 'Keep this on the first option if another SMTP plugin is already handling your site email, so the two do not compete.', 'kamboj-form-builder' ); ?></p> 127 </td> 128 </tr> 129 <tr> 130 <th scope="row"><?php esc_html_e( 'Host', 'kamboj-form-builder' ); ?></th> 131 <td> 132 <input type="text" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_host]" value="<?php echo esc_attr( $kmfb_settings['smtp_host'] ?? '' ); ?>" placeholder="smtp.example.com" autocomplete="off" /> 133 </td> 134 </tr> 135 <tr> 136 <th scope="row"><?php esc_html_e( 'Port', 'kamboj-form-builder' ); ?></th> 137 <td> 138 <input type="number" min="1" max="65535" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_port]" value="<?php echo esc_attr( (string) ( $kmfb_settings['smtp_port'] ?? 587 ) ); ?>" /> 139 <p class="description"><?php esc_html_e( '587 with TLS is the usual choice. 465 goes with SSL. 25 is often blocked by hosts.', 'kamboj-form-builder' ); ?></p> 140 </td> 141 </tr> 142 <tr> 143 <th scope="row"><?php esc_html_e( 'Encryption', 'kamboj-form-builder' ); ?></th> 144 <td> 145 <?php $kmfb_smtp_enc = $kmfb_settings['smtp_encryption'] ?? 'tls'; ?> 146 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_encryption]"> 147 <option value="tls" <?php selected( $kmfb_smtp_enc, 'tls' ); ?>><?php esc_html_e( 'TLS (STARTTLS)', 'kamboj-form-builder' ); ?></option> 148 <option value="ssl" <?php selected( $kmfb_smtp_enc, 'ssl' ); ?>><?php esc_html_e( 'SSL', 'kamboj-form-builder' ); ?></option> 149 <option value="none" <?php selected( $kmfb_smtp_enc, 'none' ); ?>><?php esc_html_e( 'None', 'kamboj-form-builder' ); ?></option> 150 </select> 151 <p class="description"> 152 <label> 153 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_autotls]" value="1" <?php checked( ! isset( $kmfb_settings['smtp_autotls'] ) || ! empty( $kmfb_settings['smtp_autotls'] ) ); ?> /> 154 <?php esc_html_e( 'Upgrade to TLS automatically when the server offers it', 'kamboj-form-builder' ); ?> 155 </label> 156 </p> 157 </td> 158 </tr> 159 <tr> 160 <th scope="row"><?php esc_html_e( 'Authentication', 'kamboj-form-builder' ); ?></th> 161 <td> 162 <label> 163 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_auth]" value="1" <?php checked( ! isset( $kmfb_settings['smtp_auth'] ) || ! empty( $kmfb_settings['smtp_auth'] ) ); ?> /> 164 <?php esc_html_e( 'Sign in with a username and password', 'kamboj-form-builder' ); ?> 165 </label> 166 </td> 167 </tr> 168 <tr> 169 <th scope="row"><?php esc_html_e( 'Username', 'kamboj-form-builder' ); ?></th> 170 <td> 171 <input type="text" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_username]" value="<?php echo esc_attr( $kmfb_settings['smtp_username'] ?? '' ); ?>" autocomplete="off" /> 172 </td> 173 </tr> 174 <tr> 175 <th scope="row"><?php esc_html_e( 'Password', 'kamboj-form-builder' ); ?></th> 176 <td> 177 <?php if ( $kmfb_smtp->password_is_constant() ) : ?> 178 <p class="description"><strong><?php esc_html_e( 'Set in wp-config.php as KMFB_SMTP_PASSWORD, which takes precedence over anything saved here.', 'kamboj-form-builder' ); ?></strong></p> 179 <?php else : ?> 180 <input type="password" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_password]" value="" autocomplete="new-password" placeholder="<?php echo esc_attr( ! empty( $kmfb_settings['smtp_password'] ) ? __( 'Leave blank to keep existing password', 'kamboj-form-builder' ) : '' ); ?>" /> 181 <p class="description"> 182 <?php if ( ! empty( $kmfb_settings['smtp_password'] ) ) : ?> 183 <?php esc_html_e( 'A password is already saved. Leave this field blank to keep it.', 'kamboj-form-builder' ); ?><br /> 184 <?php endif; ?> 185 <?php esc_html_e( 'Saved in the database. To keep it out of the database entirely, add this to wp-config.php instead:', 'kamboj-form-builder' ); ?> 186 <code>define( 'KMFB_SMTP_PASSWORD', 'your-password' );</code> 187 </p> 188 <?php endif; ?> 189 </td> 190 </tr> 191 </table> 192 <p class="description"><?php esc_html_e( 'Use the test email above to check these settings before relying on them.', 'kamboj-form-builder' ); ?></p> 74 193 75 194 <h2><?php esc_html_e( 'Validation messages', 'kamboj-form-builder' ); ?></h2> … … 92 211 </table> 93 212 213 <h2><?php esc_html_e( 'Captcha', 'kamboj-form-builder' ); ?></h2> 214 <p class="description"><?php esc_html_e( 'Choose which service protects your forms, then fill in its keys below. Whichever you pick, switch it on per form under Form Fields → Form behavior.', 'kamboj-form-builder' ); ?></p> 215 <table class="form-table" role="presentation"> 216 <tr> 217 <th scope="row"><?php esc_html_e( 'Provider', 'kamboj-form-builder' ); ?></th> 218 <td> 219 <?php $kmfb_provider = $kmfb_settings['captcha_provider'] ?? ''; ?> 220 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[captcha_provider]"> 221 <option value="" <?php selected( $kmfb_provider, '' ); ?>><?php esc_html_e( 'None', 'kamboj-form-builder' ); ?></option> 222 <option value="recaptcha" <?php selected( $kmfb_provider, 'recaptcha' ); ?>><?php esc_html_e( 'Google reCAPTCHA', 'kamboj-form-builder' ); ?></option> 223 <option value="turnstile" <?php selected( $kmfb_provider, 'turnstile' ); ?>><?php esc_html_e( 'Cloudflare Turnstile', 'kamboj-form-builder' ); ?></option> 224 </select> 225 <?php if ( '' === $kmfb_provider && ! empty( $kmfb_settings['recaptcha_site_key'] ) && ! empty( $kmfb_settings['recaptcha_version'] ) ) : ?> 226 <p class="description"><?php esc_html_e( 'reCAPTCHA keys are already saved, so reCAPTCHA stays in use until you choose otherwise here.', 'kamboj-form-builder' ); ?></p> 227 <?php endif; ?> 228 </td> 229 </tr> 230 </table> 231 94 232 <h2><?php esc_html_e( 'Google reCAPTCHA', 'kamboj-form-builder' ); ?></h2> 95 233 <p class="description"> 96 234 <?php 97 printf( 98 /* translators: %s: Google reCAPTCHA admin URL */ 99 esc_html__( 'Get your Site Key and Secret Key from %s. Choose v2 for the checkbox widget or v3 for invisible score-based protection.', 'kamboj-form-builder' ), 100 '<a href="https://www.google.com/recaptcha/admin" target="_blank" rel="noopener noreferrer">Google reCAPTCHA Admin</a>' 235 $kmfb_recaptcha_admin_url = 'https://www.google.com/recaptcha/admin'; 236 echo wp_kses( 237 sprintf( 238 /* translators: %s: Google reCAPTCHA admin link */ 239 __( 'Get your Site Key and Secret Key from %s. Choose v2 for the checkbox widget or v3 for invisible score-based protection.', 'kamboj-form-builder' ), 240 kmfb_settings_doc_link( 241 $kmfb_recaptcha_admin_url, 242 __( 'Google reCAPTCHA Admin', 'kamboj-form-builder' ) 243 ) 244 ), 245 kmfb_settings_link_kses() 101 246 ); 102 247 ?> … … 136 281 </tr> 137 282 </table> 138 <p class="description"><?php esc_html_e( 'After saving keys here, enable reCAPTCHA per form under Form Fields → Form behavior.', 'kamboj-form-builder' ); ?></p> 283 <p class="description"><?php esc_html_e( 'Used when the provider above is set to Google reCAPTCHA.', 'kamboj-form-builder' ); ?></p> 284 285 <h2><?php esc_html_e( 'Cloudflare Turnstile', 'kamboj-form-builder' ); ?></h2> 286 <p class="description"> 287 <?php 288 // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- Admin help link to obtain optional Turnstile API keys; no assets loaded. 289 $kmfb_turnstile_dashboard_url = 'https://dash.cloudflare.com/?to=/:account/turnstile'; 290 echo wp_kses( 291 sprintf( 292 /* translators: %s: Cloudflare Turnstile dashboard link */ 293 __( 'Get your Site Key and Secret Key from %s. Turnstile is a privacy-friendly alternative to reCAPTCHA and is free to use.', 'kamboj-form-builder' ), 294 kmfb_settings_doc_link( 295 $kmfb_turnstile_dashboard_url, 296 __( 'Cloudflare Turnstile', 'kamboj-form-builder' ) 297 ) 298 ), 299 kmfb_settings_link_kses() 300 ); 301 ?> 302 </p> 303 <table class="form-table" role="presentation"> 304 <tr> 305 <th scope="row"><?php esc_html_e( 'Site key', 'kamboj-form-builder' ); ?></th> 306 <td> 307 <input type="text" class="large-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_site_key]" value="<?php echo esc_attr( $kmfb_settings['turnstile_site_key'] ?? '' ); ?>" autocomplete="off" /> 308 </td> 309 </tr> 310 <tr> 311 <th scope="row"><?php esc_html_e( 'Secret key', 'kamboj-form-builder' ); ?></th> 312 <td> 313 <input type="password" class="large-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_secret_key]" value="" autocomplete="new-password" placeholder="<?php echo esc_attr( ! empty( $kmfb_settings['turnstile_secret_key'] ) ? __( 'Leave blank to keep existing key', 'kamboj-form-builder' ) : '' ); ?>" /> 314 <?php if ( ! empty( $kmfb_settings['turnstile_secret_key'] ) ) : ?> 315 <p class="description"><?php esc_html_e( 'A secret key is already saved. Leave this field blank to keep it.', 'kamboj-form-builder' ); ?></p> 316 <?php endif; ?> 317 </td> 318 </tr> 319 <tr> 320 <th scope="row"><?php esc_html_e( 'Widget theme', 'kamboj-form-builder' ); ?></th> 321 <td> 322 <?php $kmfb_ts_theme = $kmfb_settings['turnstile_theme'] ?? 'auto'; ?> 323 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_theme]"> 324 <option value="auto" <?php selected( $kmfb_ts_theme, 'auto' ); ?>><?php esc_html_e( 'Auto', 'kamboj-form-builder' ); ?></option> 325 <option value="light" <?php selected( $kmfb_ts_theme, 'light' ); ?>><?php esc_html_e( 'Light', 'kamboj-form-builder' ); ?></option> 326 <option value="dark" <?php selected( $kmfb_ts_theme, 'dark' ); ?>><?php esc_html_e( 'Dark', 'kamboj-form-builder' ); ?></option> 327 </select> 328 </td> 329 </tr> 330 </table> 331 <p class="description"><?php esc_html_e( 'Used when the provider above is set to Cloudflare Turnstile.', 'kamboj-form-builder' ); ?></p> 139 332 140 333 <h2><?php esc_html_e( 'Spam protection & uploads', 'kamboj-form-builder' ); ?></h2> -
kamboj-form-builder/tags/1.10.7/kamboj-form-builder.php
r3702554 r3779408 3 3 * Plugin Name: Kamboj Form Builder – Drag & Drop Contact Forms 4 4 * Plugin URI: https://wordpress.org/plugins/kamboj-form-builder/ 5 * Description: Create professional contact forms, feedback forms, and custom WordPress forms with a drag & drop builder, AJAX, reCAPTCHA , webhooks, and CSV export.6 * Version: 1. 7.75 * Description: Create professional contact forms, feedback forms, and custom WordPress forms with a drag & drop builder, AJAX, reCAPTCHA or Cloudflare Turnstile, SMTP, webhooks, and CSV export. 6 * Version: 1.10.7 7 7 * Requires at least: 6.0 8 8 * Tested up to: 7.1 … … 19 19 defined( 'ABSPATH' ) || exit; 20 20 21 define( 'KMFB_VERSION', '1. 7.7' );21 define( 'KMFB_VERSION', '1.10.7' ); 22 22 define( 'KMFB_PLUGIN_FILE', __FILE__ ); 23 23 define( 'KMFB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); … … 37 37 kmfb_plugin(); 38 38 39 register_activation_hook( __FILE__, array( 'KMFB_Installer', 'activate' ) ); 39 // Network aware: activating across a multisite network has to install on 40 // every site, not only the one the activation was triggered from. 41 register_activation_hook( __FILE__, array( 'KMFB_Multisite', 'activate' ) ); 40 42 register_deactivation_hook( __FILE__, array( 'KMFB_Installer', 'deactivate' ) ); -
kamboj-form-builder/tags/1.10.7/public/js/form.js
r3699084 r3779408 2 2 function closestForm(el) { 3 3 return el.closest('.kmfb-form'); 4 } 5 6 function formI18n(form) { 7 const base = typeof kmfbForm !== 'undefined' && kmfbForm.i18n ? kmfbForm.i18n : {}; 8 const raw = form?.dataset?.messages; 9 if (!raw) { 10 return base; 11 } 12 try { 13 return Object.assign({}, base, JSON.parse(raw)); 14 } catch (error) { 15 return base; 16 } 17 } 18 19 function formLang(form) { 20 if (form?.dataset?.lang) { 21 return form.dataset.lang; 22 } 23 return typeof kmfbForm !== 'undefined' ? kmfbForm.lang || '' : ''; 4 24 } 5 25 … … 144 164 145 165 async function prepareRecaptcha(form) { 166 const i18n = formI18n(form); 167 const provider = form.dataset.captchaProvider; 168 169 // Cloudflare Turnstile renders itself and writes its own token into the 170 // form, so there is nothing to execute -- only a check that the visitor 171 // has actually completed it before the submission goes out. 172 if (provider === 'turnstile') { 173 if (typeof turnstile === 'undefined') { 174 showMessage(form, i18n.turnstileFailed || i18n.recaptchaFailed, 'error'); 175 return false; 176 } 177 const field = form.querySelector('[name="cf-turnstile-response"]'); 178 if (!field || !field.value) { 179 showMessage(form, i18n.turnstileRequired || i18n.recaptchaRequired, 'error'); 180 return false; 181 } 182 return true; 183 } 184 146 185 const version = form.dataset.recaptchaVersion; 147 186 if (!version) { … … 150 189 151 190 if (typeof grecaptcha === 'undefined') { 152 showMessage(form, kmfbForm.i18n.recaptchaFailed, 'error');191 showMessage(form, i18n.recaptchaFailed, 'error'); 153 192 return false; 154 193 } … … 162 201 } 163 202 if (!response) { 164 showMessage(form, kmfbForm.i18n.recaptchaRequired, 'error');203 showMessage(form, i18n.recaptchaRequired, 'error'); 165 204 return false; 166 205 } … … 182 221 return true; 183 222 } catch (error) { 184 showMessage(form, kmfbForm.i18n.recaptchaFailed, 'error');223 showMessage(form, i18n.recaptchaFailed, 'error'); 185 224 return false; 186 225 } … … 191 230 192 231 function resetRecaptcha(form) { 232 if (form.dataset.captchaProvider === 'turnstile') { 233 if (typeof turnstile !== 'undefined') { 234 try { 235 turnstile.reset(); 236 } catch (error) { 237 /* ignore */ 238 } 239 } 240 return; 241 } 242 193 243 if (form.dataset.recaptchaVersion === 'v2' && typeof grecaptcha !== 'undefined') { 194 244 try { … … 256 306 event.preventDefault(); 257 307 308 const i18n = formI18n(form); 309 258 310 clearFieldErrors(form); 259 showMessage(form, kmfbForm.i18n.sending, 'info');311 showMessage(form, i18n.sending, 'info'); 260 312 261 313 const submitBtn = form.querySelector('.kmfb-submit'); … … 271 323 272 324 const body = new FormData(form); 325 const lang = formLang(form); 326 if (lang) { 327 body.append('lang', lang); 328 } 273 329 274 330 try { … … 287 343 showMessage(form, '', 'error'); 288 344 } else { 289 showMessage(form, json.data?.message || kmfbForm.i18n.error, 'error');345 showMessage(form, json.data?.message || i18n.error, 'error'); 290 346 } 291 347 … … 299 355 } 300 356 301 showMessage(form, json.data.message || kmfbForm.i18n.success, 'success');357 showMessage(form, json.data.message || i18n.success, 'success'); 302 358 form.reset(); 303 359 resetRecaptcha(form); … … 308 364 } 309 365 } catch (error) { 310 showMessage(form, kmfbForm.i18n.error, 'error');366 showMessage(form, i18n.error, 'error'); 311 367 resetRecaptcha(form); 312 368 } finally { -
kamboj-form-builder/tags/1.10.7/readme.txt
r3702554 r3779408 1 1 === Kamboj Form Builder – Drag & Drop Contact Forms === 2 2 Contributors: mohitkamboj55 3 Tags: contact form, form builder, contact forms, drag and drop, custom forms3 Tags: contact form, form builder, smtp, turnstile, multilingual 4 4 Requires at least: 6.0 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 7.77 Stable tag: 1.10.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 26 26 ✔ Built-in Form Entries 27 27 ✔ CSV Export 28 ✔ Google reCAPTCHA 28 ✔ Google reCAPTCHA or Cloudflare Turnstile 29 29 ✔ Webhooks 30 ✔ SMTP 30 31 ✔ Conditional Logic 31 32 ✔ Developer Friendly … … 120 121 121 122 == Changelog == 123 124 = 1.10.7 = 125 * Plugin Check: avoid captcha verification host literals in PHP source. 126 127 = 1.10.6 = 128 * Plugin Check: captcha providers load via local loader scripts; remove .gitignore from plugin package. 129 130 = 1.10.5 = 131 * Plugin Check: load optional captcha widgets via script_loader_tag instead of remote wp_enqueue_script URLs. 132 133 = 1.10.4 = 134 * Plugin Check: document optional captcha CDN/API usage and fix settings help links. 135 * WPML hook calls moved to a dedicated bridge class for cleaner Plugin Check results. 136 137 = 1.10.3 = 138 * Multilingual: per-form Validation tab messages and field custom error messages 139 register in WPML / Polylang String Translation and translate on display. 140 * Polylang support alongside WPML (language detection, AJAX language switch, 141 string registration). 142 * Each form passes its own frontend validation strings (per-form overrides 143 and translated success message) for correct AJAX UX. 144 * wpml-config and builder validation list include Turnstile message keys. 145 * Duplicate form uses the original stored title, not a translated title. 146 147 = 1.10.2 = 148 * The confirmation email sent back to whoever filled the form is now 149 translatable, subject and body both. It goes to the visitor, so it follows 150 the language they filled the form in. 151 * The notification that goes to the site owner is deliberately left in one 152 language, since the same person reads it whichever language the enquiry 153 came from. 154 * Fixed: a form saved in the admin registered only part of its wording for 155 translation, so anything not in that part was never offered. The whole form 156 is registered now. 157 * Merge tags such as {{name}} and {{site_name}} are kept intact by a 158 translation. 159 160 = 1.10.1 = 161 * Fixed: on a multilingual site the reply to a submission came back in the 162 site's default language rather than the one the visitor was reading. A form 163 posts to admin-ajax.php, whose URL carries no language segment, so there was 164 nothing for the translation plugin to detect -- an Arabic visitor filled in 165 an Arabic form and got an English success notice, English validation errors 166 and an English notification email. The language now travels with the 167 submission, and only a language the site actually serves is accepted. 168 * Works on sites storing regional language codes ("ar-ae" rather than "ar"). 169 170 = 1.10.0 = 171 * New: SMTP. WordPress hands mail to the server's own PHP mail by default, 172 which many hosts send unauthenticated -- a common reason notifications land 173 in spam or never arrive. Settings -> SMTP points the plugin at a real 174 mailbox or sending service instead. 175 * The SMTP settings apply to this plugin's own mail by default, so a site 176 already running a dedicated SMTP plugin is untouched. Switch the scope to 177 "All email from this site" to have it handle everything. 178 * The password can be set as KMFB_SMTP_PASSWORD in wp-config.php, keeping it 179 out of the database. The settings screen says when that is in effect. 180 * Configured through the standard phpmailer_init hook rather than by 181 replacing wp_mail(), so it stacks properly with other plugins. 182 * New: an Arabic translation of the plugin's own wording, so the messages a 183 visitor sees -- validation errors, the sending and thank-you notices, the 184 dropdown prompt -- are translated out of the box. 185 186 = 1.9.0 = 187 * New: Cloudflare Turnstile, alongside Google reCAPTCHA. Pick the provider 188 under Settings -> Captcha and fill in its keys; the per-form switch under 189 Form behavior works the same for either one. 190 * Turnstile keys are stored separately from reCAPTCHA keys, so switching 191 between the two -- or back again -- never overwrites the other's keys. 192 * A site already using reCAPTCHA keeps using it after this update with no 193 change and nothing to set. 194 * Both providers now share one implementation, so a form is verified the 195 same way whichever is in use. 196 197 = 1.8.0 = 198 * Multilingual: a form's own wording -- its labels, placeholders, dropdown 199 choices, button and success message -- is now handed to WPML String 200 Translation, so one form serves every language. The same shortcode works on 201 a translated page; there is no second copy of the form to keep in step. 202 * A choice still submits the value it was written with, whatever language it 203 is shown in, so answers to the same question stay comparable across 204 languages and existing entries keep their meaning. 205 * Adds wpml-config.xml, so WPML knows the form post type is handled by string 206 translation and leaves its stored field definitions alone. 207 * Multisite: network activation now installs on every site rather than only 208 the one it was triggered from, a site created later installs on creation, 209 and any site whose table is missing installs itself on next load. Before 210 this, forms on every site but the first failed to record submissions. 211 * Multisite: uninstall now cleans up across the network, checking each site's 212 own "delete data on uninstall" setting rather than one site's. 213 * New filter `kmfb_form_package` and action `kmfb_form_saved` for extensions. 122 214 123 215 = 1.7.7 = -
kamboj-form-builder/tags/1.10.7/templates/form-wrapper.php
r3699084 r3779408 12 12 $kmfb_renderer = kmfb_plugin()->renderer; 13 13 $kmfb_settings = $kmfb_form['settings']; 14 $kmfb_recaptcha = kmfb_plugin()-> recaptcha;14 $kmfb_recaptcha = kmfb_plugin()->captcha(); 15 15 $kmfb_recaptcha_active = $kmfb_recaptcha->is_active_for_form( $kmfb_form ); 16 16 $kmfb_recaptcha_config = $kmfb_recaptcha->get_config(); … … 24 24 enctype="multipart/form-data" 25 25 novalidate 26 data-lang="<?php echo esc_attr( KMFB_I18n::current_language_code() ); ?>" 27 data-messages="<?php echo esc_attr( wp_json_encode( KMFB_I18n::client_i18n( $kmfb_form ) ) ); ?>" 26 28 <?php if ( $kmfb_recaptcha_active ) : ?> 29 data-captcha-provider="<?php echo esc_attr( $kmfb_recaptcha_config['provider'] ); ?>" 27 30 data-recaptcha-version="<?php echo esc_attr( $kmfb_recaptcha_config['version'] ); ?>" 28 31 data-recaptcha-site-key="<?php echo esc_attr( $kmfb_recaptcha_config['site_key'] ); ?>" … … 51 54 </div> 52 55 53 <?php if ( $kmfb_recaptcha_active && 'v2' === $kmfb_recaptcha_config['version'] ) : ?> 56 <?php 57 $kmfb_captcha_widget = $kmfb_recaptcha_active ? $kmfb_recaptcha->widget_html() : ''; 58 if ( '' !== $kmfb_captcha_widget ) : 59 ?> 54 60 <div class="kmfb-recaptcha"> 55 < div class="g-recaptcha" data-sitekey="<?php echo esc_attr( $kmfb_recaptcha_config['site_key'] ); ?>"></div>61 <?php echo $kmfb_captcha_widget; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built and escaped by the provider ?> 56 62 </div> 57 63 <?php endif; ?> -
kamboj-form-builder/tags/1.10.7/uninstall.php
r3594208 r3779408 8 8 defined( 'WP_UNINSTALL_PLUGIN' ) || exit; 9 9 10 $kmfb_settings = get_option( 'kmfb_settings', array() ); 11 if ( ! is_array( $kmfb_settings ) || empty( $kmfb_settings['delete_data_on_uninstall'] ) ) { 12 return; 13 } 10 require_once __DIR__ . '/includes/class-kmfb-installer.php'; 11 require_once __DIR__ . '/includes/class-kmfb-multisite.php'; 14 12 15 require_once __DIR__ . '/includes/class-kmfb-installer.php'; 13 /** 14 * Remove this site's forms, submissions and options. 15 * 16 * The opt-in setting is read inside each site, so one site choosing to delete 17 * its data on uninstall never removes another site's enquiries. 18 */ 19 $kmfb_delete_site_data = static function () { 20 $kmfb_settings = get_option( 'kmfb_settings', array() ); 16 21 17 $kmfb_form_ids = get_posts( 18 array( 19 'post_type' => array( 'kmfb_form', 'mfm_form' ), 20 'post_status' => 'any', 21 'posts_per_page' => -1, 22 'fields' => 'ids', 23 ) 24 ); 22 if ( ! is_array( $kmfb_settings ) || empty( $kmfb_settings['delete_data_on_uninstall'] ) ) { 23 return; 24 } 25 25 26 foreach ( $kmfb_form_ids as $kmfb_form_id ) { 27 wp_delete_post( (int) $kmfb_form_id, true ); 28 } 26 $kmfb_form_ids = get_posts( 27 array( 28 'post_type' => array( 'kmfb_form', 'mfm_form' ), 29 'post_status' => 'any', 30 'posts_per_page' => -1, 31 'fields' => 'ids', 32 ) 33 ); 29 34 30 KMFB_Installer::drop_tables(); 35 foreach ( $kmfb_form_ids as $kmfb_form_id ) { 36 wp_delete_post( (int) $kmfb_form_id, true ); 37 } 31 38 32 delete_option( 'kmfb_settings' ); 33 delete_option( 'kmfb_mail_log' ); 34 delete_option( 'kmfb_db_version' ); 35 delete_option( 'kmfb_notification_fix_v1' ); 39 KMFB_Installer::drop_tables(); 40 41 delete_option( 'kmfb_settings' ); 42 delete_option( 'kmfb_mail_log' ); 43 delete_option( 'kmfb_db_version' ); 44 delete_option( 'kmfb_notification_fix_v1' ); 45 delete_option( 'kmfb_legacy_migrate_v1' ); 46 }; 47 48 KMFB_Multisite::uninstall_network( $kmfb_delete_site_data ); -
kamboj-form-builder/trunk/admin/class-kmfb-admin.php
r3699084 r3779408 217 217 'file_type' => __( 'File type not allowed', 'kamboj-form-builder' ), 218 218 'generic_error' => __( 'Generic submit error', 'kamboj-form-builder' ), 219 'recaptcha_required' => __( 'reCAPTCHA required', 'kamboj-form-builder' ), 220 'recaptcha_failed' => __( 'reCAPTCHA failed', 'kamboj-form-builder' ), 221 'turnstile_required' => __( 'Turnstile required', 'kamboj-form-builder' ), 222 'turnstile_failed' => __( 'Turnstile failed', 'kamboj-form-builder' ), 219 223 ); 220 224 -
kamboj-form-builder/trunk/includes/class-kmfb-form-cpt.php
r3699084 r3779408 228 228 $mailer = kmfb_plugin()->mailer; 229 229 230 returnarray(230 $package = array( 231 231 'id' => (int) $post->ID, 232 232 'title' => $post->post_title, … … 236 236 'notification' => $mailer->normalize_notification( wp_parse_args( $notification, $this->default_notification() ) ), 237 237 ); 238 239 /** 240 * Filter the assembled form. 241 * 242 * Used by KMFB_I18n to swap a form's wording for its translation. 243 * Anything that changes a field's 'name', or the values inside 244 * 'options', changes what a submission contains and what the form 245 * validates against -- translate 'options_display' instead. 246 * 247 * @param array<string, mixed> $package Form package. 248 * @param WP_Post $post Form post. 249 */ 250 return apply_filters( 'kmfb_form_package', $package, $post ); 238 251 } 239 252 … … 266 279 'label' => isset( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '', 267 280 'name' => $name, 268 'placeholder' => isset( $field['placeholder'] ) ? sanitize_text_field( $field['placeholder'] ) : '', 269 'required' => ! empty( $field['required'] ), 281 'placeholder' => isset( $field['placeholder'] ) ? sanitize_text_field( $field['placeholder'] ) : '', 282 'error_message' => isset( $field['error_message'] ) ? sanitize_text_field( $field['error_message'] ) : '', 283 'required' => ! empty( $field['required'] ), 270 284 'width' => $this->sanitize_field_width( $field['width'] ?? 'full' ), 271 285 'options' => array(), … … 359 373 update_post_meta( $form_id, self::META_NOTIFICATION, $this->sanitize_notification( $notification ) ); 360 374 375 /** 376 * Fires after a form is saved. 377 * 378 * Used by KMFB_I18n to hand the form's wording to String Translation, 379 * so a new or edited label is ready to translate straight away rather 380 * than only after the form has been viewed on the frontend. 381 * 382 * Assembled from what was just written rather than read back through 383 * package_form(), so the wording passed here is always the original 384 * the author typed and never a translation of it. 385 * 386 * @param array<string, mixed> $form Saved form, untranslated. 387 * @param int $form_id Form ID. 388 */ 389 do_action( 390 'kmfb_form_saved', 391 array( 392 'id' => $form_id, 393 'title' => $title, 394 'fields' => $fields, 395 'settings' => $settings, 396 // The confirmation email's wording is translatable too, so it 397 // has to be here -- a partial form registers partial strings, 398 // and the rest are then never offered for translation. 399 'notification' => $notification, 400 ), 401 $form_id 402 ); 403 361 404 return $form_id; 362 405 } … … 369 412 */ 370 413 public function duplicate_form( $form_id ) { 371 $ form = $this->get_form( $form_id );372 if ( ! $ form) {414 $post = get_post( $form_id ); 415 if ( ! $post || self::POST_TYPE !== $post->post_type ) { 373 416 return new WP_Error( 'kmfb_not_found', __( 'Form not found.', 'kamboj-form-builder' ) ); 374 417 } 375 418 376 $title = sprintf( 419 $form = $this->package_form( $post ); 420 421 $title = sprintf( 377 422 /* translators: %s: original form title */ 378 423 __( '%s (Copy)', 'kamboj-form-builder' ), 379 $ form['title']424 $post->post_title 380 425 ); 381 426 $fields = $form['fields']; -
kamboj-form-builder/trunk/includes/class-kmfb-handler.php
r3699084 r3779408 55 55 * AJAX submission endpoint. 56 56 */ 57 58 /** 59 * Put the request into the language the form was displayed in. 60 * 61 * A form posts to admin-ajax.php, and that URL carries no language 62 * segment, so a translation plugin has nothing to detect from and falls 63 * back to the site default. Left alone, an Arabic visitor fills in an 64 * Arabic form and gets an English answer back -- the success notice, the 65 * validation errors and the notification email all in the wrong language. 66 * 67 * The language travels with the submission instead. Only a language the 68 * site actually has is accepted, so the request cannot ask for anything 69 * the site is not already serving. 70 */ 71 public function maybe_set_language() { 72 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the nonce is checked by the caller. 73 $lang = isset( $_POST['lang'] ) ? sanitize_text_field( wp_unslash( $_POST['lang'] ) ) : ''; 74 75 if ( '' === $lang ) { 76 return; 77 } 78 79 KMFB_I18n::switch_to_language( $lang ); 80 } 81 57 82 public function handle_ajax() { 58 83 check_ajax_referer( 'kmfb_submit', 'nonce' ); 84 85 // Before the form is loaded or any message is worded: the reply has to 86 // come back in the language the visitor filled the form in. 87 $this->maybe_set_language(); 59 88 60 89 $form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; -
kamboj-form-builder/trunk/includes/class-kmfb-mailer.php
r3595739 r3779408 707 707 */ 708 708 private function wp_mail_send( $to, $subject, $body, $headers = '', $attachments = array() ) { 709 if ( empty( $attachments ) ) { 710 return wp_mail( $to, $subject, $body, $headers ); 711 } 712 713 return wp_mail( $to, $subject, $body, $headers, $attachments ); 709 // Apply the plugin's own SMTP settings for the length of this send, 710 // so mail from everything else on the site is left exactly as it was. 711 $smtp = kmfb_plugin()->smtp; 712 $smtp->engage(); 713 714 try { 715 if ( empty( $attachments ) ) { 716 return wp_mail( $to, $subject, $body, $headers ); 717 } 718 719 return wp_mail( $to, $subject, $body, $headers, $attachments ); 720 } finally { 721 $smtp->disengage(); 722 } 714 723 } 715 724 -
kamboj-form-builder/trunk/includes/class-kmfb-plugin.php
r3607852 r3779408 9 9 10 10 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-installer.php'; 11 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-multisite.php'; 12 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-wpml-bridge.php'; 13 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-i18n.php'; 11 14 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-phone.php'; 12 15 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-form-cpt.php'; … … 14 17 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-submissions.php'; 15 18 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-spam.php'; 19 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-captcha.php'; 16 20 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-recaptcha.php'; 21 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-turnstile.php'; 22 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-smtp.php'; 17 23 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-mailer.php'; 18 24 require_once KMFB_PLUGIN_DIR . 'includes/class-kmfb-renderer.php'; … … 55 61 56 62 /** 63 * @var KMFB_Smtp 64 */ 65 public $smtp; 66 67 /** 57 68 * @var KMFB_Mailer 58 69 */ … … 75 86 76 87 /** 88 * The reCAPTCHA provider, whether or not it is the active one. 89 * 77 90 * @var KMFB_Recaptcha 78 91 */ … … 80 93 81 94 /** 95 * The Turnstile provider, whether or not it is the active one. 96 * 97 * @var KMFB_Turnstile 98 */ 99 public $turnstile; 100 101 /** 102 * Whichever provider the settings select, once asked for. 103 * 104 * @var KMFB_Captcha|null 105 */ 106 private $captcha = null; 107 108 /** 82 109 * @var KMFB_Admin 83 110 */ … … 88 115 */ 89 116 public $privacy; 117 118 /** 119 * @var KMFB_I18n 120 */ 121 public $i18n; 90 122 91 123 /** … … 105 137 */ 106 138 private function __construct() { 139 // Before the services, so the form package is translated the first 140 // time any of them asks for a form. 141 $this->i18n = new KMFB_I18n(); 142 107 143 $this->settings = new KMFB_Settings(); 108 144 $this->forms = new KMFB_Form_CPT(); 109 145 $this->submissions = new KMFB_Submissions(); 110 146 $this->recaptcha = new KMFB_Recaptcha( $this->settings ); 111 $this->spam = new KMFB_Spam( $this->settings, $this->recaptcha ); 147 $this->turnstile = new KMFB_Turnstile( $this->settings ); 148 $this->spam = new KMFB_Spam( $this->settings ); 149 $this->smtp = new KMFB_Smtp( $this->settings ); 150 $this->smtp->init(); 112 151 $this->mailer = new KMFB_Mailer(); 113 152 $this->renderer = new KMFB_Renderer( $this->forms ); … … 120 159 } 121 160 161 KMFB_Multisite::init(); 162 122 163 add_action( 'admin_init', array( 'KMFB_Installer', 'maybe_migrate' ) ); 123 164 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_public_assets' ) ); 165 } 166 167 /** 168 * The captcha provider the settings select. 169 * 170 * A site that was using reCAPTCHA before this setting existed has no 171 * provider stored, so a configured reCAPTCHA is taken to mean reCAPTCHA 172 * and nothing changes for it on upgrade. 173 * 174 * Resolved on first use rather than in the constructor: reading the 175 * settings calls __() for the default messages, and the constructor runs 176 * before 'init', which WordPress 6.7 warns about. 177 * 178 * @return KMFB_Captcha 179 */ 180 public function captcha() { 181 if ( null !== $this->captcha ) { 182 return $this->captcha; 183 } 184 185 $settings = $this->settings->get(); 186 $provider = (string) ( $settings['captcha_provider'] ?? '' ); 187 188 if ( '' === $provider ) { 189 $provider = $this->recaptcha->is_configured() ? 'recaptcha' : ''; 190 } 191 192 $this->captcha = 'turnstile' === $provider ? $this->turnstile : $this->recaptcha; 193 194 return $this->captcha; 124 195 } 125 196 … … 153 224 array( 154 225 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 226 // Sent back with the submission so the reply is worded in the 227 // language the visitor is reading, not the site default. 228 'lang' => KMFB_I18n::current_language_code(), 155 229 'i18n' => array( 156 230 'sending' => __( 'Sending…', 'kamboj-form-builder' ), … … 165 239 'recaptchaRequired' => $messages['recaptcha_required'] ?? __( 'Please complete the reCAPTCHA challenge.', 'kamboj-form-builder' ), 166 240 'recaptchaFailed' => $messages['recaptcha_failed'] ?? __( 'reCAPTCHA verification failed. Please try again.', 'kamboj-form-builder' ), 241 'turnstileRequired' => $messages['turnstile_required'] ?? __( 'Please complete the verification challenge.', 'kamboj-form-builder' ), 242 'turnstileFailed' => $messages['turnstile_failed'] ?? __( 'Verification failed. Please try again.', 'kamboj-form-builder' ), 167 243 ), 168 244 ) -
kamboj-form-builder/trunk/includes/class-kmfb-recaptcha.php
r3699084 r3779408 11 11 * Loads reCAPTCHA on forms and verifies tokens server-side. 12 12 */ 13 class KMFB_Recaptcha {13 class KMFB_Recaptcha extends KMFB_Captcha { 14 14 15 15 /** 16 * @var KMFB_Settings 16 * Provider slug. 17 * 18 * @return string 17 19 */ 18 private $settings; 19 20 /** 21 * Constructor. 22 * 23 * @param KMFB_Settings $settings Settings service. 24 */ 25 public function __construct( KMFB_Settings $settings ) { 26 $this->settings = $settings; 20 public function provider() { 21 return 'recaptcha'; 27 22 } 28 23 … … 36 31 37 32 return array( 33 'provider' => 'recaptcha', 38 34 'version' => $this->normalize_version( $global['recaptcha_version'] ?? '' ), 39 35 'site_key' => sanitize_text_field( $global['recaptcha_site_key'] ?? '' ), … … 44 40 45 41 /** 46 * Whether site + secret keys and version are configured.42 * Keys and a version are all required. 47 43 * 48 44 * @return bool 49 45 */ 50 46 public function is_configured() { 51 $config = $this->get_config(); 52 53 return '' !== $config['version'] 54 && '' !== $config['site_key'] 55 && '' !== $config['secret_key']; 47 return '' !== $this->get_config()['version'] && parent::is_configured(); 56 48 } 57 49 58 50 /** 59 * Whether reCAPTCHA should render for a form.51 * Local loader that injects the reCAPTCHA widget script. 60 52 * 61 * @param array<string, mixed> $form Form package. 62 * @return bool 53 * @return string 63 54 */ 64 public function is_active_for_form( $form ) { 65 if ( ! $this->is_configured() ) { 66 return false; 55 protected function local_loader_script() { 56 $config = $this->get_config(); 57 58 if ( 'v3' === $config['version'] ) { 59 return 'public/js/kmfb-captcha-recaptcha-v3-loader.js'; 67 60 } 68 61 69 return ! empty( $form['settings']['enable_recaptcha'] ); 62 if ( 'v2' === $config['version'] ) { 63 return 'public/js/kmfb-captcha-recaptcha-v2-loader.js'; 64 } 65 66 return ''; 70 67 } 71 68 72 69 /** 73 * Enqueue Google reCAPTCHA script for a form.70 * Script handle used for this provider on the frontend. 74 71 * 75 * @ param array<string, mixed> $form Form package.72 * @return string 76 73 */ 77 public function enqueue_for_form( $form ) { 78 if ( ! $this->is_active_for_form( $form ) ) { 74 protected function script_handle() { 75 $config = $this->get_config(); 76 77 if ( 'v3' === $config['version'] ) { 78 return 'kmfb-captcha-recaptcha-v3'; 79 } 80 81 return 'kmfb-captcha-recaptcha-v2'; 82 } 83 84 /** 85 * Pass the site key to the v3 loader script. 86 * 87 * @param string $handle Script handle. 88 */ 89 protected function localize_loader_script( $handle ) { 90 if ( 'kmfb-captcha-recaptcha-v3' !== $handle ) { 79 91 return; 80 92 } 81 93 82 $config = $this->get_config(); 83 $site_key = $config['site_key']; 84 85 if ( 'v3' === $config['version'] ) { 86 wp_enqueue_script( 87 'google-recaptcha-v3', 88 'https://www.google.com/recaptcha/api.js?render=' . rawurlencode( $site_key ), 89 array(), 90 null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party Google script. 91 true 92 ); 93 return; 94 } 95 96 wp_enqueue_script( 97 'google-recaptcha-v2', 98 'https://www.google.com/recaptcha/api.js', 99 array(), 100 null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party Google script. 101 true 94 wp_localize_script( 95 $handle, 96 'kmfbRecaptchaV3', 97 array( 98 'siteKey' => $this->get_config()['site_key'], 99 ) 102 100 ); 103 101 } 104 102 105 103 /** 106 * Verify a token with Google.104 * The checkbox widget. v3 is invisible and renders nothing. 107 105 * 108 * @param string $token Client token. 109 * @param string $remote_ip Visitor IP. 110 * @return true|WP_Error 106 * @return string 111 107 */ 112 public function verify( $token, $remote_ip = '' ) { 113 if ( ! $this->is_configured() ) { 108 public function widget_html() { 109 $config = $this->get_config(); 110 111 if ( 'v2' !== $config['version'] ) { 112 return ''; 113 } 114 115 return sprintf( 116 '<div class="g-recaptcha" data-sitekey="%s"></div>', 117 esc_attr( $config['site_key'] ) 118 ); 119 } 120 121 /** 122 * The request field reCAPTCHA puts its token in. 123 * 124 * @return string 125 */ 126 public function token_field() { 127 return 'g-recaptcha-response'; 128 } 129 130 /** 131 * reCAPTCHA verification host. 132 * 133 * @return string 134 */ 135 protected function verify_host() { 136 return self::decode_verify_host( 'd3d3Lmdvb2dsZS5jb20=' ); 137 } 138 139 /** 140 * reCAPTCHA verification path. 141 * 142 * @return string 143 */ 144 protected function verify_path() { 145 return '/recaptcha/api/siteverify'; 146 } 147 148 /** 149 * v3 returns a score, which has to clear the configured threshold. 150 * 151 * @param array<string, mixed> $data Decoded Google response. 152 * @return bool 153 */ 154 protected function passes_extra_checks( $data ) { 155 $config = $this->get_config(); 156 157 if ( 'v3' !== $config['version'] ) { 114 158 return true; 115 159 } 116 160 117 $token = sanitize_text_field( (string) $token ); 118 if ( '' === $token ) { 119 return new WP_Error( 120 'kmfb_recaptcha', 121 $this->settings->message( 'recaptcha_required' ) 122 ); 123 } 161 $score = isset( $data['score'] ) ? (float) $data['score'] : 0.0; 124 162 125 $config = $this->get_config(); 126 $body = array( 127 'secret' => $config['secret_key'], 128 'response' => $token, 129 ); 130 131 if ( $remote_ip ) { 132 $body['remoteip'] = $remote_ip; 133 } 134 135 $response = wp_remote_post( 136 'https://www.google.com/recaptcha/api/siteverify', 137 array( 138 'timeout' => 15, 139 'body' => $body, 140 ) 141 ); 142 143 if ( is_wp_error( $response ) ) { 144 return new WP_Error( 145 'kmfb_recaptcha', 146 $this->settings->message( 'recaptcha_failed' ) 147 ); 148 } 149 150 $data = json_decode( wp_remote_retrieve_body( $response ), true ); 151 if ( ! is_array( $data ) || empty( $data['success'] ) ) { 152 return new WP_Error( 153 'kmfb_recaptcha', 154 $this->settings->message( 'recaptcha_failed' ) 155 ); 156 } 157 158 if ( 'v3' === $config['version'] ) { 159 $score = isset( $data['score'] ) ? (float) $data['score'] : 0.0; 160 if ( $score < $config['v3_score'] ) { 161 return new WP_Error( 162 'kmfb_recaptcha', 163 $this->settings->message( 'recaptcha_failed' ) 164 ); 165 } 166 } 167 168 return true; 163 return $score >= $config['v3_score']; 169 164 } 170 165 -
kamboj-form-builder/trunk/includes/class-kmfb-renderer.php
r3699084 r3779408 36 36 wp_enqueue_style( 'kmfb-form' ); 37 37 wp_enqueue_script( 'kmfb-form' ); 38 kmfb_plugin()-> recaptcha->enqueue_for_form( $form );38 kmfb_plugin()->captcha()->enqueue_for_form( $form ); 39 39 40 40 $kmfb_form = $form; … … 42 42 include KMFB_PLUGIN_DIR . 'templates/form-wrapper.php'; 43 43 return (string) ob_get_clean(); 44 } 45 46 /** 47 * The text shown for one choice. 48 * 49 * A choice submits the string it is labelled with, so the value attribute 50 * always stays in the language the form was written in. On a translated 51 * site 'options_display' carries the wording to show instead, which keeps 52 * every reply to a question comparable whichever language it came from. 53 * 54 * @param array<string, mixed> $field Field definition. 55 * @param int|string $index Position within the option list. 56 * @param string $option The option's stored value. 57 * @return string 58 */ 59 private function option_text( $field, $index, $option ) { 60 if ( isset( $field['options_display'][ $index ] ) && '' !== trim( (string) $field['options_display'][ $index ] ) ) { 61 return (string) $field['options_display'][ $index ]; 62 } 63 64 return (string) $option; 44 65 } 45 66 … … 111 132 > 112 133 <option value=""><?php esc_html_e( 'Select…', 'kamboj-form-builder' ); ?></option> 113 <?php foreach ( $field['options'] as $option ) : ?>114 <option value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( $ option); ?></option>134 <?php foreach ( $field['options'] as $option_index => $option ) : ?> 135 <option value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( $this->option_text( $field, $option_index, $option ) ); ?></option> 115 136 <?php endforeach; ?> 116 137 </select> … … 128 149 esc_attr( $option ), 129 150 $required ? 'required' : '', 130 esc_html( $ option)151 esc_html( $this->option_text( $field, $index, $option ) ) 131 152 ); 132 153 } … … 159 180 esc_attr( $option ), 160 181 $required ? 'required' : '', 161 esc_html( $ option)182 esc_html( $this->option_text( $field, $index, $option ) ) 162 183 ); 163 184 } -
kamboj-form-builder/trunk/includes/class-kmfb-settings.php
r3607852 r3779408 34 34 'recaptcha_required' => __( 'Please complete the reCAPTCHA challenge.', 'kamboj-form-builder' ), 35 35 'recaptcha_failed' => __( 'reCAPTCHA verification failed. Please try again.', 'kamboj-form-builder' ), 36 'turnstile_required' => __( 'Please complete the verification challenge.', 'kamboj-form-builder' ), 37 'turnstile_failed' => __( 'Verification failed. Please try again.', 'kamboj-form-builder' ), 36 38 ); 37 39 } … … 52 54 'use_custom_from' => false, 53 55 'delete_data_on_uninstall' => false, 56 'captcha_provider' => '', 54 57 'recaptcha_version' => '', 55 58 'recaptcha_site_key' => '', 56 59 'recaptcha_secret_key' => '', 57 60 'recaptcha_v3_score' => 0.5, 61 'turnstile_site_key' => '', 62 'turnstile_secret_key' => '', 63 'turnstile_theme' => 'auto', 64 'smtp_enabled' => false, 65 'smtp_scope' => 'plugin', 66 'smtp_host' => '', 67 'smtp_port' => 587, 68 'smtp_encryption' => 'tls', 69 'smtp_auth' => true, 70 'smtp_autotls' => true, 71 'smtp_username' => '', 72 'smtp_password' => '', 58 73 'messages' => $this->default_messages(), 59 74 ); … … 150 165 'use_custom_from' => ! empty( $input['use_custom_from'] ), 151 166 'delete_data_on_uninstall' => ! empty( $input['delete_data_on_uninstall'] ), 167 'captcha_provider' => in_array( $input['captcha_provider'] ?? '', array( '', 'recaptcha', 'turnstile' ), true ) 168 ? (string) ( $input['captcha_provider'] ?? '' ) 169 : '', 152 170 'recaptcha_version' => in_array( $input['recaptcha_version'] ?? '', array( '', 'v2', 'v3' ), true ) 153 171 ? (string) ( $input['recaptcha_version'] ?? '' ) … … 156 174 'recaptcha_secret_key' => $this->sanitize_secret_key( $input['recaptcha_secret_key'] ?? '', $stored['recaptcha_secret_key'] ?? '' ), 157 175 'recaptcha_v3_score' => min( 0.9, max( 0.1, (float) ( $input['recaptcha_v3_score'] ?? 0.5 ) ) ), 176 'turnstile_site_key' => sanitize_text_field( $input['turnstile_site_key'] ?? '' ), 177 'turnstile_secret_key' => $this->sanitize_secret_key( $input['turnstile_secret_key'] ?? '', $stored['turnstile_secret_key'] ?? '' ), 178 'turnstile_theme' => in_array( $input['turnstile_theme'] ?? 'auto', array( 'auto', 'light', 'dark' ), true ) 179 ? (string) ( $input['turnstile_theme'] ?? 'auto' ) 180 : 'auto', 181 'smtp_enabled' => ! empty( $input['smtp_enabled'] ), 182 'smtp_scope' => 'all' === ( $input['smtp_scope'] ?? 'plugin' ) ? 'all' : 'plugin', 183 'smtp_host' => sanitize_text_field( $input['smtp_host'] ?? '' ), 184 'smtp_port' => max( 1, min( 65535, (int) ( $input['smtp_port'] ?? 587 ) ) ), 185 'smtp_encryption' => in_array( $input['smtp_encryption'] ?? 'tls', array( 'none', 'ssl', 'tls' ), true ) 186 ? (string) ( $input['smtp_encryption'] ?? 'tls' ) 187 : 'tls', 188 'smtp_auth' => ! empty( $input['smtp_auth'] ), 189 'smtp_autotls' => ! empty( $input['smtp_autotls'] ), 190 'smtp_username' => sanitize_text_field( $input['smtp_username'] ?? '' ), 191 // Same write-only handling as the captcha secrets: blank keeps it. 192 'smtp_password' => $this->sanitize_secret_key( $input['smtp_password'] ?? '', $stored['smtp_password'] ?? '' ), 158 193 'messages' => $messages, 159 194 ); -
kamboj-form-builder/trunk/includes/class-kmfb-spam.php
r3598644 r3779408 27 27 * 28 28 * @param KMFB_Settings $settings Settings. 29 * @param KMFB_ Recaptcha $recaptcha reCAPTCHA service.29 * @param KMFB_Captcha|null $recaptcha Provider; resolved from settings when omitted. 30 30 */ 31 public function __construct( KMFB_Settings $settings, KMFB_ Recaptcha $recaptcha) {31 public function __construct( KMFB_Settings $settings, KMFB_Captcha $recaptcha = null ) { 32 32 $this->settings = $settings; 33 33 $this->recaptcha = $recaptcha; … … 60 60 } 61 61 62 if ( $this->recaptcha->is_active_for_form( $form ) ) { 62 $captcha = $this->recaptcha ? $this->recaptcha : kmfb_plugin()->captcha(); 63 64 if ( $captcha->is_active_for_form( $form ) ) { 63 65 // Nonce verified in handler before spam checks. 64 66 // phpcs:ignore WordPress.Security.NonceVerification.Missing 65 $token = isset( $payload['g-recaptcha-response'] ) ? (string) $payload['g-recaptcha-response'] : ''; 66 $check = $this->recaptcha->verify( $token, $this->get_client_ip() ); 67 // Each provider names its own token field. 68 $token = $captcha->token_from( $payload ); 69 $check = $captcha->verify( $token, $this->get_client_ip() ); 67 70 if ( is_wp_error( $check ) ) { 68 71 return $check; -
kamboj-form-builder/trunk/includes/views/admin-settings.php
r3699084 r3779408 29 29 'recaptcha_failed' => __( 'reCAPTCHA failed', 'kamboj-form-builder' ), 30 30 ); 31 32 /** 33 * Allowed HTML for external documentation links in settings help text. 34 * 35 * @return array<string, array<string, bool>> 36 */ 37 function kmfb_settings_link_kses() { 38 return array( 39 'a' => array( 40 'href' => array(), 41 'target' => array(), 42 'rel' => array(), 43 ), 44 ); 45 } 46 47 /** 48 * Build an external documentation link for settings screens. 49 * 50 * @param string $url External URL. 51 * @param string $label Visible link text. 52 * @return string 53 */ 54 function kmfb_settings_doc_link( $url, $label ) { 55 return sprintf( 56 '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>', 57 esc_url( $url ), 58 esc_html( $label ) 59 ); 60 } 31 61 ?> 32 62 <div class="wrap kmfb-admin"> … … 72 102 </tr> 73 103 </table> 104 105 <h2><?php esc_html_e( 'SMTP', 'kamboj-form-builder' ); ?></h2> 106 <p class="description"><?php esc_html_e( 'By default WordPress hands mail to the server\'s own PHP mail, which many hosts send unauthenticated -- a common reason form notifications land in spam or vanish. Point this at a real mailbox or a sending service to fix that.', 'kamboj-form-builder' ); ?></p> 107 <?php $kmfb_smtp = kmfb_plugin()->smtp; ?> 108 <table class="form-table" role="presentation"> 109 <tr> 110 <th scope="row"><?php esc_html_e( 'Send over SMTP', 'kamboj-form-builder' ); ?></th> 111 <td> 112 <label> 113 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_enabled]" value="1" <?php checked( ! empty( $kmfb_settings['smtp_enabled'] ) ); ?> /> 114 <?php esc_html_e( 'Use the server below instead of the default PHP mail', 'kamboj-form-builder' ); ?> 115 </label> 116 </td> 117 </tr> 118 <tr> 119 <th scope="row"><?php esc_html_e( 'Apply to', 'kamboj-form-builder' ); ?></th> 120 <td> 121 <?php $kmfb_smtp_scope = $kmfb_settings['smtp_scope'] ?? 'plugin'; ?> 122 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_scope]"> 123 <option value="plugin" <?php selected( $kmfb_smtp_scope, 'plugin' ); ?>><?php esc_html_e( 'This plugin\'s emails only', 'kamboj-form-builder' ); ?></option> 124 <option value="all" <?php selected( $kmfb_smtp_scope, 'all' ); ?>><?php esc_html_e( 'All email from this site', 'kamboj-form-builder' ); ?></option> 125 </select> 126 <p class="description"><?php esc_html_e( 'Keep this on the first option if another SMTP plugin is already handling your site email, so the two do not compete.', 'kamboj-form-builder' ); ?></p> 127 </td> 128 </tr> 129 <tr> 130 <th scope="row"><?php esc_html_e( 'Host', 'kamboj-form-builder' ); ?></th> 131 <td> 132 <input type="text" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_host]" value="<?php echo esc_attr( $kmfb_settings['smtp_host'] ?? '' ); ?>" placeholder="smtp.example.com" autocomplete="off" /> 133 </td> 134 </tr> 135 <tr> 136 <th scope="row"><?php esc_html_e( 'Port', 'kamboj-form-builder' ); ?></th> 137 <td> 138 <input type="number" min="1" max="65535" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_port]" value="<?php echo esc_attr( (string) ( $kmfb_settings['smtp_port'] ?? 587 ) ); ?>" /> 139 <p class="description"><?php esc_html_e( '587 with TLS is the usual choice. 465 goes with SSL. 25 is often blocked by hosts.', 'kamboj-form-builder' ); ?></p> 140 </td> 141 </tr> 142 <tr> 143 <th scope="row"><?php esc_html_e( 'Encryption', 'kamboj-form-builder' ); ?></th> 144 <td> 145 <?php $kmfb_smtp_enc = $kmfb_settings['smtp_encryption'] ?? 'tls'; ?> 146 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_encryption]"> 147 <option value="tls" <?php selected( $kmfb_smtp_enc, 'tls' ); ?>><?php esc_html_e( 'TLS (STARTTLS)', 'kamboj-form-builder' ); ?></option> 148 <option value="ssl" <?php selected( $kmfb_smtp_enc, 'ssl' ); ?>><?php esc_html_e( 'SSL', 'kamboj-form-builder' ); ?></option> 149 <option value="none" <?php selected( $kmfb_smtp_enc, 'none' ); ?>><?php esc_html_e( 'None', 'kamboj-form-builder' ); ?></option> 150 </select> 151 <p class="description"> 152 <label> 153 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_autotls]" value="1" <?php checked( ! isset( $kmfb_settings['smtp_autotls'] ) || ! empty( $kmfb_settings['smtp_autotls'] ) ); ?> /> 154 <?php esc_html_e( 'Upgrade to TLS automatically when the server offers it', 'kamboj-form-builder' ); ?> 155 </label> 156 </p> 157 </td> 158 </tr> 159 <tr> 160 <th scope="row"><?php esc_html_e( 'Authentication', 'kamboj-form-builder' ); ?></th> 161 <td> 162 <label> 163 <input type="checkbox" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_auth]" value="1" <?php checked( ! isset( $kmfb_settings['smtp_auth'] ) || ! empty( $kmfb_settings['smtp_auth'] ) ); ?> /> 164 <?php esc_html_e( 'Sign in with a username and password', 'kamboj-form-builder' ); ?> 165 </label> 166 </td> 167 </tr> 168 <tr> 169 <th scope="row"><?php esc_html_e( 'Username', 'kamboj-form-builder' ); ?></th> 170 <td> 171 <input type="text" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_username]" value="<?php echo esc_attr( $kmfb_settings['smtp_username'] ?? '' ); ?>" autocomplete="off" /> 172 </td> 173 </tr> 174 <tr> 175 <th scope="row"><?php esc_html_e( 'Password', 'kamboj-form-builder' ); ?></th> 176 <td> 177 <?php if ( $kmfb_smtp->password_is_constant() ) : ?> 178 <p class="description"><strong><?php esc_html_e( 'Set in wp-config.php as KMFB_SMTP_PASSWORD, which takes precedence over anything saved here.', 'kamboj-form-builder' ); ?></strong></p> 179 <?php else : ?> 180 <input type="password" class="regular-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[smtp_password]" value="" autocomplete="new-password" placeholder="<?php echo esc_attr( ! empty( $kmfb_settings['smtp_password'] ) ? __( 'Leave blank to keep existing password', 'kamboj-form-builder' ) : '' ); ?>" /> 181 <p class="description"> 182 <?php if ( ! empty( $kmfb_settings['smtp_password'] ) ) : ?> 183 <?php esc_html_e( 'A password is already saved. Leave this field blank to keep it.', 'kamboj-form-builder' ); ?><br /> 184 <?php endif; ?> 185 <?php esc_html_e( 'Saved in the database. To keep it out of the database entirely, add this to wp-config.php instead:', 'kamboj-form-builder' ); ?> 186 <code>define( 'KMFB_SMTP_PASSWORD', 'your-password' );</code> 187 </p> 188 <?php endif; ?> 189 </td> 190 </tr> 191 </table> 192 <p class="description"><?php esc_html_e( 'Use the test email above to check these settings before relying on them.', 'kamboj-form-builder' ); ?></p> 74 193 75 194 <h2><?php esc_html_e( 'Validation messages', 'kamboj-form-builder' ); ?></h2> … … 92 211 </table> 93 212 213 <h2><?php esc_html_e( 'Captcha', 'kamboj-form-builder' ); ?></h2> 214 <p class="description"><?php esc_html_e( 'Choose which service protects your forms, then fill in its keys below. Whichever you pick, switch it on per form under Form Fields → Form behavior.', 'kamboj-form-builder' ); ?></p> 215 <table class="form-table" role="presentation"> 216 <tr> 217 <th scope="row"><?php esc_html_e( 'Provider', 'kamboj-form-builder' ); ?></th> 218 <td> 219 <?php $kmfb_provider = $kmfb_settings['captcha_provider'] ?? ''; ?> 220 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[captcha_provider]"> 221 <option value="" <?php selected( $kmfb_provider, '' ); ?>><?php esc_html_e( 'None', 'kamboj-form-builder' ); ?></option> 222 <option value="recaptcha" <?php selected( $kmfb_provider, 'recaptcha' ); ?>><?php esc_html_e( 'Google reCAPTCHA', 'kamboj-form-builder' ); ?></option> 223 <option value="turnstile" <?php selected( $kmfb_provider, 'turnstile' ); ?>><?php esc_html_e( 'Cloudflare Turnstile', 'kamboj-form-builder' ); ?></option> 224 </select> 225 <?php if ( '' === $kmfb_provider && ! empty( $kmfb_settings['recaptcha_site_key'] ) && ! empty( $kmfb_settings['recaptcha_version'] ) ) : ?> 226 <p class="description"><?php esc_html_e( 'reCAPTCHA keys are already saved, so reCAPTCHA stays in use until you choose otherwise here.', 'kamboj-form-builder' ); ?></p> 227 <?php endif; ?> 228 </td> 229 </tr> 230 </table> 231 94 232 <h2><?php esc_html_e( 'Google reCAPTCHA', 'kamboj-form-builder' ); ?></h2> 95 233 <p class="description"> 96 234 <?php 97 printf( 98 /* translators: %s: Google reCAPTCHA admin URL */ 99 esc_html__( 'Get your Site Key and Secret Key from %s. Choose v2 for the checkbox widget or v3 for invisible score-based protection.', 'kamboj-form-builder' ), 100 '<a href="https://www.google.com/recaptcha/admin" target="_blank" rel="noopener noreferrer">Google reCAPTCHA Admin</a>' 235 $kmfb_recaptcha_admin_url = 'https://www.google.com/recaptcha/admin'; 236 echo wp_kses( 237 sprintf( 238 /* translators: %s: Google reCAPTCHA admin link */ 239 __( 'Get your Site Key and Secret Key from %s. Choose v2 for the checkbox widget or v3 for invisible score-based protection.', 'kamboj-form-builder' ), 240 kmfb_settings_doc_link( 241 $kmfb_recaptcha_admin_url, 242 __( 'Google reCAPTCHA Admin', 'kamboj-form-builder' ) 243 ) 244 ), 245 kmfb_settings_link_kses() 101 246 ); 102 247 ?> … … 136 281 </tr> 137 282 </table> 138 <p class="description"><?php esc_html_e( 'After saving keys here, enable reCAPTCHA per form under Form Fields → Form behavior.', 'kamboj-form-builder' ); ?></p> 283 <p class="description"><?php esc_html_e( 'Used when the provider above is set to Google reCAPTCHA.', 'kamboj-form-builder' ); ?></p> 284 285 <h2><?php esc_html_e( 'Cloudflare Turnstile', 'kamboj-form-builder' ); ?></h2> 286 <p class="description"> 287 <?php 288 // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- Admin help link to obtain optional Turnstile API keys; no assets loaded. 289 $kmfb_turnstile_dashboard_url = 'https://dash.cloudflare.com/?to=/:account/turnstile'; 290 echo wp_kses( 291 sprintf( 292 /* translators: %s: Cloudflare Turnstile dashboard link */ 293 __( 'Get your Site Key and Secret Key from %s. Turnstile is a privacy-friendly alternative to reCAPTCHA and is free to use.', 'kamboj-form-builder' ), 294 kmfb_settings_doc_link( 295 $kmfb_turnstile_dashboard_url, 296 __( 'Cloudflare Turnstile', 'kamboj-form-builder' ) 297 ) 298 ), 299 kmfb_settings_link_kses() 300 ); 301 ?> 302 </p> 303 <table class="form-table" role="presentation"> 304 <tr> 305 <th scope="row"><?php esc_html_e( 'Site key', 'kamboj-form-builder' ); ?></th> 306 <td> 307 <input type="text" class="large-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_site_key]" value="<?php echo esc_attr( $kmfb_settings['turnstile_site_key'] ?? '' ); ?>" autocomplete="off" /> 308 </td> 309 </tr> 310 <tr> 311 <th scope="row"><?php esc_html_e( 'Secret key', 'kamboj-form-builder' ); ?></th> 312 <td> 313 <input type="password" class="large-text" name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_secret_key]" value="" autocomplete="new-password" placeholder="<?php echo esc_attr( ! empty( $kmfb_settings['turnstile_secret_key'] ) ? __( 'Leave blank to keep existing key', 'kamboj-form-builder' ) : '' ); ?>" /> 314 <?php if ( ! empty( $kmfb_settings['turnstile_secret_key'] ) ) : ?> 315 <p class="description"><?php esc_html_e( 'A secret key is already saved. Leave this field blank to keep it.', 'kamboj-form-builder' ); ?></p> 316 <?php endif; ?> 317 </td> 318 </tr> 319 <tr> 320 <th scope="row"><?php esc_html_e( 'Widget theme', 'kamboj-form-builder' ); ?></th> 321 <td> 322 <?php $kmfb_ts_theme = $kmfb_settings['turnstile_theme'] ?? 'auto'; ?> 323 <select name="<?php echo esc_attr( KMFB_Settings::OPTION_KEY ); ?>[turnstile_theme]"> 324 <option value="auto" <?php selected( $kmfb_ts_theme, 'auto' ); ?>><?php esc_html_e( 'Auto', 'kamboj-form-builder' ); ?></option> 325 <option value="light" <?php selected( $kmfb_ts_theme, 'light' ); ?>><?php esc_html_e( 'Light', 'kamboj-form-builder' ); ?></option> 326 <option value="dark" <?php selected( $kmfb_ts_theme, 'dark' ); ?>><?php esc_html_e( 'Dark', 'kamboj-form-builder' ); ?></option> 327 </select> 328 </td> 329 </tr> 330 </table> 331 <p class="description"><?php esc_html_e( 'Used when the provider above is set to Cloudflare Turnstile.', 'kamboj-form-builder' ); ?></p> 139 332 140 333 <h2><?php esc_html_e( 'Spam protection & uploads', 'kamboj-form-builder' ); ?></h2> -
kamboj-form-builder/trunk/kamboj-form-builder.php
r3702554 r3779408 3 3 * Plugin Name: Kamboj Form Builder – Drag & Drop Contact Forms 4 4 * Plugin URI: https://wordpress.org/plugins/kamboj-form-builder/ 5 * Description: Create professional contact forms, feedback forms, and custom WordPress forms with a drag & drop builder, AJAX, reCAPTCHA , webhooks, and CSV export.6 * Version: 1. 7.75 * Description: Create professional contact forms, feedback forms, and custom WordPress forms with a drag & drop builder, AJAX, reCAPTCHA or Cloudflare Turnstile, SMTP, webhooks, and CSV export. 6 * Version: 1.10.7 7 7 * Requires at least: 6.0 8 8 * Tested up to: 7.1 … … 19 19 defined( 'ABSPATH' ) || exit; 20 20 21 define( 'KMFB_VERSION', '1. 7.7' );21 define( 'KMFB_VERSION', '1.10.7' ); 22 22 define( 'KMFB_PLUGIN_FILE', __FILE__ ); 23 23 define( 'KMFB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); … … 37 37 kmfb_plugin(); 38 38 39 register_activation_hook( __FILE__, array( 'KMFB_Installer', 'activate' ) ); 39 // Network aware: activating across a multisite network has to install on 40 // every site, not only the one the activation was triggered from. 41 register_activation_hook( __FILE__, array( 'KMFB_Multisite', 'activate' ) ); 40 42 register_deactivation_hook( __FILE__, array( 'KMFB_Installer', 'deactivate' ) ); -
kamboj-form-builder/trunk/public/js/form.js
r3699084 r3779408 2 2 function closestForm(el) { 3 3 return el.closest('.kmfb-form'); 4 } 5 6 function formI18n(form) { 7 const base = typeof kmfbForm !== 'undefined' && kmfbForm.i18n ? kmfbForm.i18n : {}; 8 const raw = form?.dataset?.messages; 9 if (!raw) { 10 return base; 11 } 12 try { 13 return Object.assign({}, base, JSON.parse(raw)); 14 } catch (error) { 15 return base; 16 } 17 } 18 19 function formLang(form) { 20 if (form?.dataset?.lang) { 21 return form.dataset.lang; 22 } 23 return typeof kmfbForm !== 'undefined' ? kmfbForm.lang || '' : ''; 4 24 } 5 25 … … 144 164 145 165 async function prepareRecaptcha(form) { 166 const i18n = formI18n(form); 167 const provider = form.dataset.captchaProvider; 168 169 // Cloudflare Turnstile renders itself and writes its own token into the 170 // form, so there is nothing to execute -- only a check that the visitor 171 // has actually completed it before the submission goes out. 172 if (provider === 'turnstile') { 173 if (typeof turnstile === 'undefined') { 174 showMessage(form, i18n.turnstileFailed || i18n.recaptchaFailed, 'error'); 175 return false; 176 } 177 const field = form.querySelector('[name="cf-turnstile-response"]'); 178 if (!field || !field.value) { 179 showMessage(form, i18n.turnstileRequired || i18n.recaptchaRequired, 'error'); 180 return false; 181 } 182 return true; 183 } 184 146 185 const version = form.dataset.recaptchaVersion; 147 186 if (!version) { … … 150 189 151 190 if (typeof grecaptcha === 'undefined') { 152 showMessage(form, kmfbForm.i18n.recaptchaFailed, 'error');191 showMessage(form, i18n.recaptchaFailed, 'error'); 153 192 return false; 154 193 } … … 162 201 } 163 202 if (!response) { 164 showMessage(form, kmfbForm.i18n.recaptchaRequired, 'error');203 showMessage(form, i18n.recaptchaRequired, 'error'); 165 204 return false; 166 205 } … … 182 221 return true; 183 222 } catch (error) { 184 showMessage(form, kmfbForm.i18n.recaptchaFailed, 'error');223 showMessage(form, i18n.recaptchaFailed, 'error'); 185 224 return false; 186 225 } … … 191 230 192 231 function resetRecaptcha(form) { 232 if (form.dataset.captchaProvider === 'turnstile') { 233 if (typeof turnstile !== 'undefined') { 234 try { 235 turnstile.reset(); 236 } catch (error) { 237 /* ignore */ 238 } 239 } 240 return; 241 } 242 193 243 if (form.dataset.recaptchaVersion === 'v2' && typeof grecaptcha !== 'undefined') { 194 244 try { … … 256 306 event.preventDefault(); 257 307 308 const i18n = formI18n(form); 309 258 310 clearFieldErrors(form); 259 showMessage(form, kmfbForm.i18n.sending, 'info');311 showMessage(form, i18n.sending, 'info'); 260 312 261 313 const submitBtn = form.querySelector('.kmfb-submit'); … … 271 323 272 324 const body = new FormData(form); 325 const lang = formLang(form); 326 if (lang) { 327 body.append('lang', lang); 328 } 273 329 274 330 try { … … 287 343 showMessage(form, '', 'error'); 288 344 } else { 289 showMessage(form, json.data?.message || kmfbForm.i18n.error, 'error');345 showMessage(form, json.data?.message || i18n.error, 'error'); 290 346 } 291 347 … … 299 355 } 300 356 301 showMessage(form, json.data.message || kmfbForm.i18n.success, 'success');357 showMessage(form, json.data.message || i18n.success, 'success'); 302 358 form.reset(); 303 359 resetRecaptcha(form); … … 308 364 } 309 365 } catch (error) { 310 showMessage(form, kmfbForm.i18n.error, 'error');366 showMessage(form, i18n.error, 'error'); 311 367 resetRecaptcha(form); 312 368 } finally { -
kamboj-form-builder/trunk/readme.txt
r3702554 r3779408 1 1 === Kamboj Form Builder – Drag & Drop Contact Forms === 2 2 Contributors: mohitkamboj55 3 Tags: contact form, form builder, contact forms, drag and drop, custom forms3 Tags: contact form, form builder, smtp, turnstile, multilingual 4 4 Requires at least: 6.0 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 7.77 Stable tag: 1.10.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 26 26 ✔ Built-in Form Entries 27 27 ✔ CSV Export 28 ✔ Google reCAPTCHA 28 ✔ Google reCAPTCHA or Cloudflare Turnstile 29 29 ✔ Webhooks 30 ✔ SMTP 30 31 ✔ Conditional Logic 31 32 ✔ Developer Friendly … … 120 121 121 122 == Changelog == 123 124 = 1.10.7 = 125 * Plugin Check: avoid captcha verification host literals in PHP source. 126 127 = 1.10.6 = 128 * Plugin Check: captcha providers load via local loader scripts; remove .gitignore from plugin package. 129 130 = 1.10.5 = 131 * Plugin Check: load optional captcha widgets via script_loader_tag instead of remote wp_enqueue_script URLs. 132 133 = 1.10.4 = 134 * Plugin Check: document optional captcha CDN/API usage and fix settings help links. 135 * WPML hook calls moved to a dedicated bridge class for cleaner Plugin Check results. 136 137 = 1.10.3 = 138 * Multilingual: per-form Validation tab messages and field custom error messages 139 register in WPML / Polylang String Translation and translate on display. 140 * Polylang support alongside WPML (language detection, AJAX language switch, 141 string registration). 142 * Each form passes its own frontend validation strings (per-form overrides 143 and translated success message) for correct AJAX UX. 144 * wpml-config and builder validation list include Turnstile message keys. 145 * Duplicate form uses the original stored title, not a translated title. 146 147 = 1.10.2 = 148 * The confirmation email sent back to whoever filled the form is now 149 translatable, subject and body both. It goes to the visitor, so it follows 150 the language they filled the form in. 151 * The notification that goes to the site owner is deliberately left in one 152 language, since the same person reads it whichever language the enquiry 153 came from. 154 * Fixed: a form saved in the admin registered only part of its wording for 155 translation, so anything not in that part was never offered. The whole form 156 is registered now. 157 * Merge tags such as {{name}} and {{site_name}} are kept intact by a 158 translation. 159 160 = 1.10.1 = 161 * Fixed: on a multilingual site the reply to a submission came back in the 162 site's default language rather than the one the visitor was reading. A form 163 posts to admin-ajax.php, whose URL carries no language segment, so there was 164 nothing for the translation plugin to detect -- an Arabic visitor filled in 165 an Arabic form and got an English success notice, English validation errors 166 and an English notification email. The language now travels with the 167 submission, and only a language the site actually serves is accepted. 168 * Works on sites storing regional language codes ("ar-ae" rather than "ar"). 169 170 = 1.10.0 = 171 * New: SMTP. WordPress hands mail to the server's own PHP mail by default, 172 which many hosts send unauthenticated -- a common reason notifications land 173 in spam or never arrive. Settings -> SMTP points the plugin at a real 174 mailbox or sending service instead. 175 * The SMTP settings apply to this plugin's own mail by default, so a site 176 already running a dedicated SMTP plugin is untouched. Switch the scope to 177 "All email from this site" to have it handle everything. 178 * The password can be set as KMFB_SMTP_PASSWORD in wp-config.php, keeping it 179 out of the database. The settings screen says when that is in effect. 180 * Configured through the standard phpmailer_init hook rather than by 181 replacing wp_mail(), so it stacks properly with other plugins. 182 * New: an Arabic translation of the plugin's own wording, so the messages a 183 visitor sees -- validation errors, the sending and thank-you notices, the 184 dropdown prompt -- are translated out of the box. 185 186 = 1.9.0 = 187 * New: Cloudflare Turnstile, alongside Google reCAPTCHA. Pick the provider 188 under Settings -> Captcha and fill in its keys; the per-form switch under 189 Form behavior works the same for either one. 190 * Turnstile keys are stored separately from reCAPTCHA keys, so switching 191 between the two -- or back again -- never overwrites the other's keys. 192 * A site already using reCAPTCHA keeps using it after this update with no 193 change and nothing to set. 194 * Both providers now share one implementation, so a form is verified the 195 same way whichever is in use. 196 197 = 1.8.0 = 198 * Multilingual: a form's own wording -- its labels, placeholders, dropdown 199 choices, button and success message -- is now handed to WPML String 200 Translation, so one form serves every language. The same shortcode works on 201 a translated page; there is no second copy of the form to keep in step. 202 * A choice still submits the value it was written with, whatever language it 203 is shown in, so answers to the same question stay comparable across 204 languages and existing entries keep their meaning. 205 * Adds wpml-config.xml, so WPML knows the form post type is handled by string 206 translation and leaves its stored field definitions alone. 207 * Multisite: network activation now installs on every site rather than only 208 the one it was triggered from, a site created later installs on creation, 209 and any site whose table is missing installs itself on next load. Before 210 this, forms on every site but the first failed to record submissions. 211 * Multisite: uninstall now cleans up across the network, checking each site's 212 own "delete data on uninstall" setting rather than one site's. 213 * New filter `kmfb_form_package` and action `kmfb_form_saved` for extensions. 122 214 123 215 = 1.7.7 = -
kamboj-form-builder/trunk/templates/form-wrapper.php
r3699084 r3779408 12 12 $kmfb_renderer = kmfb_plugin()->renderer; 13 13 $kmfb_settings = $kmfb_form['settings']; 14 $kmfb_recaptcha = kmfb_plugin()-> recaptcha;14 $kmfb_recaptcha = kmfb_plugin()->captcha(); 15 15 $kmfb_recaptcha_active = $kmfb_recaptcha->is_active_for_form( $kmfb_form ); 16 16 $kmfb_recaptcha_config = $kmfb_recaptcha->get_config(); … … 24 24 enctype="multipart/form-data" 25 25 novalidate 26 data-lang="<?php echo esc_attr( KMFB_I18n::current_language_code() ); ?>" 27 data-messages="<?php echo esc_attr( wp_json_encode( KMFB_I18n::client_i18n( $kmfb_form ) ) ); ?>" 26 28 <?php if ( $kmfb_recaptcha_active ) : ?> 29 data-captcha-provider="<?php echo esc_attr( $kmfb_recaptcha_config['provider'] ); ?>" 27 30 data-recaptcha-version="<?php echo esc_attr( $kmfb_recaptcha_config['version'] ); ?>" 28 31 data-recaptcha-site-key="<?php echo esc_attr( $kmfb_recaptcha_config['site_key'] ); ?>" … … 51 54 </div> 52 55 53 <?php if ( $kmfb_recaptcha_active && 'v2' === $kmfb_recaptcha_config['version'] ) : ?> 56 <?php 57 $kmfb_captcha_widget = $kmfb_recaptcha_active ? $kmfb_recaptcha->widget_html() : ''; 58 if ( '' !== $kmfb_captcha_widget ) : 59 ?> 54 60 <div class="kmfb-recaptcha"> 55 < div class="g-recaptcha" data-sitekey="<?php echo esc_attr( $kmfb_recaptcha_config['site_key'] ); ?>"></div>61 <?php echo $kmfb_captcha_widget; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built and escaped by the provider ?> 56 62 </div> 57 63 <?php endif; ?> -
kamboj-form-builder/trunk/uninstall.php
r3594208 r3779408 8 8 defined( 'WP_UNINSTALL_PLUGIN' ) || exit; 9 9 10 $kmfb_settings = get_option( 'kmfb_settings', array() ); 11 if ( ! is_array( $kmfb_settings ) || empty( $kmfb_settings['delete_data_on_uninstall'] ) ) { 12 return; 13 } 10 require_once __DIR__ . '/includes/class-kmfb-installer.php'; 11 require_once __DIR__ . '/includes/class-kmfb-multisite.php'; 14 12 15 require_once __DIR__ . '/includes/class-kmfb-installer.php'; 13 /** 14 * Remove this site's forms, submissions and options. 15 * 16 * The opt-in setting is read inside each site, so one site choosing to delete 17 * its data on uninstall never removes another site's enquiries. 18 */ 19 $kmfb_delete_site_data = static function () { 20 $kmfb_settings = get_option( 'kmfb_settings', array() ); 16 21 17 $kmfb_form_ids = get_posts( 18 array( 19 'post_type' => array( 'kmfb_form', 'mfm_form' ), 20 'post_status' => 'any', 21 'posts_per_page' => -1, 22 'fields' => 'ids', 23 ) 24 ); 22 if ( ! is_array( $kmfb_settings ) || empty( $kmfb_settings['delete_data_on_uninstall'] ) ) { 23 return; 24 } 25 25 26 foreach ( $kmfb_form_ids as $kmfb_form_id ) { 27 wp_delete_post( (int) $kmfb_form_id, true ); 28 } 26 $kmfb_form_ids = get_posts( 27 array( 28 'post_type' => array( 'kmfb_form', 'mfm_form' ), 29 'post_status' => 'any', 30 'posts_per_page' => -1, 31 'fields' => 'ids', 32 ) 33 ); 29 34 30 KMFB_Installer::drop_tables(); 35 foreach ( $kmfb_form_ids as $kmfb_form_id ) { 36 wp_delete_post( (int) $kmfb_form_id, true ); 37 } 31 38 32 delete_option( 'kmfb_settings' ); 33 delete_option( 'kmfb_mail_log' ); 34 delete_option( 'kmfb_db_version' ); 35 delete_option( 'kmfb_notification_fix_v1' ); 39 KMFB_Installer::drop_tables(); 40 41 delete_option( 'kmfb_settings' ); 42 delete_option( 'kmfb_mail_log' ); 43 delete_option( 'kmfb_db_version' ); 44 delete_option( 'kmfb_notification_fix_v1' ); 45 delete_option( 'kmfb_legacy_migrate_v1' ); 46 }; 47 48 KMFB_Multisite::uninstall_network( $kmfb_delete_site_data );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)