Changeset 3763733
- Timestamp:
- 10/24/2026 09:00:21 PM (3 hours ago)
- Location:
- 2ranq-forms
- Files:
-
- 39 edited
- 10 copied
-
tags/1.2.0 (copied) (copied from 2ranq-forms/trunk )
-
tags/1.2.0/2ranq-forms.php (copied) (copied from 2ranq-forms/trunk/2ranq-forms.php ) (2 diffs)
-
tags/1.2.0/assets/js/builder.js (copied) (copied from 2ranq-forms/trunk/assets/js/builder.js )
-
tags/1.2.0/includes/admin/class-ranq-forms-entries-screen.php (copied) (copied from 2ranq-forms/trunk/includes/admin/class-ranq-forms-entries-screen.php )
-
tags/1.2.0/includes/class-ranq-forms-entries.php (copied) (copied from 2ranq-forms/trunk/includes/class-ranq-forms-entries.php )
-
tags/1.2.0/includes/class-ranq-forms-form-settings.php (copied) (copied from 2ranq-forms/trunk/includes/class-ranq-forms-form-settings.php )
-
tags/1.2.0/includes/class-ranq-forms-notification-template.php (copied) (copied from 2ranq-forms/trunk/includes/class-ranq-forms-notification-template.php )
-
tags/1.2.0/includes/class-ranq-forms-notifications.php (copied) (copied from 2ranq-forms/trunk/includes/class-ranq-forms-notifications.php )
-
tags/1.2.0/includes/class-ranq-forms-spam.php (modified) (4 diffs)
-
tags/1.2.0/includes/class-ranq-forms-submission.php (modified) (4 diffs)
-
tags/1.2.0/includes/fields/abstract-ranq-forms-field-choices.php (copied) (copied from 2ranq-forms/trunk/includes/fields/abstract-ranq-forms-field-choices.php ) (1 diff)
-
tags/1.2.0/includes/fields/abstract-ranq-forms-field-input.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/abstract-ranq-forms-field.php (modified) (1 diff)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-address.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-checkbox.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-email.php (modified) (3 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-imagechoice.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-name.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-number.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-password.php (modified) (1 diff)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-phone.php (modified) (3 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-radio.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-ranking.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-rating.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-select.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-text.php (modified) (2 diffs)
-
tags/1.2.0/includes/fields/class-ranq-forms-field-textarea.php (modified) (2 diffs)
-
tags/1.2.0/readme.txt (copied) (copied from 2ranq-forms/trunk/readme.txt ) (5 diffs)
-
trunk/2ranq-forms.php (modified) (2 diffs)
-
trunk/includes/class-ranq-forms-spam.php (modified) (4 diffs)
-
trunk/includes/class-ranq-forms-submission.php (modified) (4 diffs)
-
trunk/includes/fields/abstract-ranq-forms-field-choices.php (modified) (1 diff)
-
trunk/includes/fields/abstract-ranq-forms-field-input.php (modified) (2 diffs)
-
trunk/includes/fields/abstract-ranq-forms-field.php (modified) (1 diff)
-
trunk/includes/fields/class-ranq-forms-field-address.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-checkbox.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-email.php (modified) (3 diffs)
-
trunk/includes/fields/class-ranq-forms-field-imagechoice.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-name.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-number.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-password.php (modified) (1 diff)
-
trunk/includes/fields/class-ranq-forms-field-phone.php (modified) (3 diffs)
-
trunk/includes/fields/class-ranq-forms-field-radio.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-ranking.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-rating.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-select.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-text.php (modified) (2 diffs)
-
trunk/includes/fields/class-ranq-forms-field-textarea.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
2ranq-forms/tags/1.2.0/2ranq-forms.php
r3688493 r3763733 4 4 * Plugin URI: https://2ranq.com/plugins/2ranq-forms/ 5 5 * Description: Form builder with conditional logic, multi-step forms and file uploads. Every feature free, nothing locked. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 6.6 8 8 * Requires PHP: 7.4 … … 33 33 */ 34 34 35 define( 'RANQ_FORMS_VERSION', '1. 1.0' );35 define( 'RANQ_FORMS_VERSION', '1.2.0' ); 36 36 define( 'RANQ_FORMS_FILE', __FILE__ ); 37 37 define( 'RANQ_FORMS_DIR', plugin_dir_path( __FILE__ ) ); -
2ranq-forms/tags/1.2.0/includes/class-ranq-forms-spam.php
r3681524 r3763733 3 3 * Spam protection, with the keys of outside services. 4 4 * 5 * Google reCAPTCHA (v2 checkbox, v2 invisible, v3) and hCaptcha, all unlocked. 6 * The owner enters the keys once, and every form can use them. 7 * 8 * Cloudflare Turnstile WORKED and was taken out of the first release, because 9 * the official Plugin Check flags every `cloudflare.com` and rejects the 10 * submission by machine. The whole removed code and the procedure for putting it 11 * back are in `13-TURNSTILE-VRACANJE.md`. 5 * Google reCAPTCHA (v2 checkbox, v2 invisible, v3), hCaptcha and Cloudflare 6 * Turnstile, all unlocked. The owner enters the keys once, and every form can 7 * use them. 8 * 9 * Turnstile sat out the first release: the Plugin Check "offloading" sniff 10 * flags every `cloudflare.com` address because Cloudflare also serves other 11 * people's files, and an error of that level rejects a NEW submission by 12 * machine. Turnstile is not file offloading, it is a visitor check, the same 13 * as the two services above it that the sniff does not flag. It came back in 14 * 1.2.0, with the sniff told so on each of the two addresses. 12 15 * 13 16 * The check runs ON THE SERVER. Without that the protection is worth nothing, … … 81 84 'script' => 'https://js.hcaptcha.com/1/api.js', 82 85 ), 86 'turnstile' => array( 87 'name' => __( 'Cloudflare Turnstile', '2ranq-forms' ), 88 'note' => __( 'Free, quiet, and usually invisible to the visitor. The easiest of the three.', '2ranq-forms' ), 89 // The dashboard address under the button the owner presses to go and 90 // make their keys, the same as the two services above have. Nothing 91 // is loaded from it, so it is not offloading either. 92 'link' => 'https://dash.cloudflare.com/?to=/:account/turnstile', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 93 'link_text' => __( 'Open Turnstile in Cloudflare', '2ranq-forms' ), 94 'steps' => array( 95 __( 'Make a free Cloudflare account. Your site does not have to use Cloudflare for anything else.', '2ranq-forms' ), 96 __( 'Open Turnstile, press Add widget, type your domain, and leave the mode on Managed.', '2ranq-forms' ), 97 __( 'Copy the Site Key and the Secret Key into the two boxes below.', '2ranq-forms' ), 98 ), 99 // The two addresses below are the visitor check itself, not content 100 // offloading: nothing of the site is served from them, they only 101 // answer whether the visitor passed. The sniff cannot tell the two 102 // apart because both live on cloudflare.com, so it is told here. 103 'verify' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 104 'script' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 105 ), 83 106 ); 84 107 } … … 221 244 } 222 245 223 $klasa = 'hcaptcha' === $s['provider'] ? 'h-captcha' : 'g-recaptcha';246 $klasa = 'hcaptcha' === $s['provider'] ? 'h-captcha' : ( 'turnstile' === $s['provider'] ? 'cf-turnstile' : 'g-recaptcha' ); 224 247 $extra = ''; 225 248 … … 304 327 if ( '' === $odgovor ) { 305 328 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the same. 329 $odgovor = isset( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : ''; 330 } 331 332 if ( '' === $odgovor ) { 333 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the same. 306 334 $odgovor = isset( $_POST['ranq_spam_token'] ) ? sanitize_text_field( wp_unslash( $_POST['ranq_spam_token'] ) ) : ''; 307 335 } -
2ranq-forms/tags/1.2.0/includes/class-ranq-forms-submission.php
r3681524 r3763733 44 44 } 45 45 46 if ( ! isset( $_POST['ranq_forms_id'] , $_POST['ranq_forms_nonce']) ) {46 if ( ! isset( $_POST['ranq_forms_id'] ) ) { 47 47 return; 48 48 } … … 51 51 52 52 if ( ! $form_id ) { 53 return;54 }55 56 $nonce = sanitize_text_field( wp_unslash( $_POST['ranq_forms_nonce'] ) );57 58 if ( ! wp_verify_nonce( $nonce, 'ranq_forms_submit_' . $form_id ) ) {59 return;60 }61 62 // The bot trap. The field sits in the form, moved off the screen, and a person63 // neither sees it nor can fill it in with the keyboard. A robot that fills in64 // everything it finds fills that in too. It is rejected quietly, with no65 // message, because a message tells the robot where the mistake is.66 if ( ! empty( $_POST['ranq_hp'] ) ) {67 53 return; 68 54 } … … 72 58 // swallow the error and the visitor would stare at a blank form with nothing. 73 59 self::$submitted = $form_id; 60 61 // The nonce is only asked of a logged-in visitor, because only there it does 62 // its real job: it stops another site from riding that visitor's session. A 63 // guest has no session to ride, so for a guest the nonce protected nothing a 64 // robot could not get around by fetching a fresh page, and it did real harm: 65 // a cache plugin happily serves the same page for a week while the nonce in 66 // it dies after a day, so a visitor sending from a cached page was thrown 67 // away without a word. Contact Form 7 and Gravity Forms treat a public form 68 // the same way. What actually guards a public form are the bot trap and the 69 // rate limit below, which do not age with the page. 70 if ( is_user_logged_in() ) { 71 72 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the nonce is only compared, never stored or printed. 73 $nonce = isset( $_POST['ranq_forms_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ranq_forms_nonce'] ) ) : ''; 74 75 if ( ! wp_verify_nonce( $nonce, 'ranq_forms_submit_' . $form_id ) ) { 76 77 // Never silently. The page the visitor sent from had simply been open, 78 // or cached, for longer than the nonce lives. They get told, and the 79 // form below gives them their typed values back, so nothing is lost. 80 self::$errors = array( '_form' => __( 'This page had been open for too long. Please send the form again.', '2ranq-forms' ) ); 81 82 return; 83 } 84 } 85 86 // The bot trap. The field sits in the form, moved off the screen, and a person 87 // neither sees it nor can fill it in with the keyboard. A robot that fills in 88 // everything it finds fills that in too. The message is deliberately vague, it 89 // does not say which check failed, so a robot learns nothing from it. But it 90 // is no longer silent, because a browser extension that autofills every field 91 // it finds can trip the trap for a real person. That person gets their text 92 // back, the trap field renders empty again, and the second send goes through. 93 if ( ! empty( $_POST['ranq_hp'] ) ) { 94 self::$errors = array( '_form' => __( 'The form could not be sent. Please try again.', '2ranq-forms' ) ); 95 96 return; 97 } 74 98 75 99 // The rate limit. Without this one person can fill the database and, through … … 286 310 287 311 return self::$sent && self::$submitted === (int) $form_id; 312 } 313 314 /** 315 * What the visitor sent for one field, raw, or null. 316 * 317 * It only answers while THIS form's send has just failed, so the fields can 318 * give the visitor their typed values back instead of a blank form. Any other 319 * time, for any other form on the page, and after a send that went through, 320 * it stays quiet and the field falls back to its default. The value comes 321 * back raw on purpose: only the field's own class knows how to clean it, so 322 * cleaning happens there, never here. 323 * 324 * @param int $form_id The form id. 325 * @param string $key The field key. 326 * 327 * @return mixed The raw sent value, or null when there is nothing to give back. 328 */ 329 public static function posted_raw( $form_id, $key ) { 330 331 if ( self::$sent || self::$submitted !== (int) $form_id ) { 332 return null; 333 } 334 335 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read only to hand the visitor's text back to the field, which cleans it with its own sanitize() before printing. 336 $raw = isset( $_POST['ranq_fields'] ) && is_array( $_POST['ranq_fields'] ) ? wp_unslash( $_POST['ranq_fields'] ) : array(); 337 338 return isset( $raw[ $key ] ) ? $raw[ $key ] : null; 288 339 } 289 340 -
2ranq-forms/tags/1.2.0/includes/fields/abstract-ranq-forms-field-choices.php
r3688493 r3763733 107 107 108 108 /** 109 * Whether one choice renders as picked. 110 * 111 * While the form is fresh the owner's defaults decide. The moment a send has 112 * failed, what the visitor picked wins, even when they picked nothing, because 113 * putting the defaults back would quietly change their answer under them. 114 * 115 * @param array $choice One choice, as get_choices() returns it. 116 * @param mixed $sticky What sticky_value() gave for the whole field. 117 * 118 * @return bool 119 */ 120 protected function choice_on( $choice, $sticky ) { 121 122 if ( null === $sticky ) { 123 return ! empty( $choice['default'] ); 124 } 125 126 $picked = array_map( 'strval', array_filter( is_array( $sticky ) ? $sticky : array( $sticky ), 'is_scalar' ) ); 127 128 return in_array( (string) $choice['value'], $picked, true ); 129 } 130 131 /** 109 132 * The value prepared for display, as the label the visitor actually saw. 110 133 * -
2ranq-forms/tags/1.2.0/includes/fields/abstract-ranq-forms-field-input.php
r3681524 r3763733 77 77 $mode = $this->input_mode(); 78 78 $auto = $this->autocomplete(); 79 80 // After a failed send the visitor's own text comes back, otherwise the 81 // owner's default. The two never mix: a failed send with an emptied field 82 // stays empty, because the default would change the answer under them. 83 $sticky = $this->sticky_value( $field, $form_id ); 84 $value = is_string( $sticky ) 85 ? $sticky 86 : ( empty( $field['default_value'] ) ? '' : (string) $field['default_value'] ); 79 87 ?> 80 88 <div class="ranq-field ranq-field--<?php echo esc_attr( $this->get_type() ); ?><?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 94 102 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" 95 103 <?php endif; ?> 96 <?php if ( ! empty( $field['default_value'] )) : ?>97 value="<?php echo esc_attr( $ field['default_value']); ?>"104 <?php if ( '' !== $value ) : ?> 105 value="<?php echo esc_attr( $value ); ?>" 98 106 <?php endif; ?> 99 107 <?php if ( $describedby ) : ?> -
2ranq-forms/tags/1.2.0/includes/fields/abstract-ranq-forms-field.php
r3681524 r3763733 329 329 330 330 /** 331 * The value the field shows when the form comes back after a failed send. 332 * 333 * A send can fail long after the visitor wrote their text: a required field 334 * was missed, the spam check said no, the page had been cached for a week. A 335 * blank form at that moment throws the whole message away, and nobody types 336 * it twice. So every field that can, gives the text back. The value is 337 * cleaned by the field's own sanitize(), because what came in is as raw as 338 * any other sent value. Null means there is nothing to give back, and the 339 * field falls back to its default. A password and a file stay out of this on 340 * purpose: a password is never printed back into a page, and a browser does 341 * not allow a file input to be prefilled at all. 342 * 343 * @param array $field The field settings. 344 * @param int $form_id The form id. 345 * 346 * @return mixed The cleaned sent value, or null when there is none. 347 */ 348 protected function sticky_value( $field, $form_id ) { 349 350 $raw = Ranq_Forms_Submission::posted_raw( $form_id, $this->field_key( $field ) ); 351 352 if ( null === $raw ) { 353 return null; 354 } 355 356 return $this->sanitize( $raw, $field ); 357 } 358 359 /** 331 360 * The element id in the HTML, has to be unique per form. 332 361 * -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-address.php
r3681524 r3763733 103 103 $describedby = $this->describedby( $field, $form_id ); 104 104 $base = $this->input_id( $field, $form_id ); 105 106 // After a failed send the visitor's address comes back instead of blank boxes. 107 $sticky = $this->sticky_value( $field, $form_id ); 105 108 ?> 106 109 <div class="ranq-field ranq-field--address<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 124 127 class="ranq-field__input" 125 128 autocomplete="<?php echo esc_attr( $part[1] ); ?>" 129 <?php if ( is_array( $sticky ) && isset( $sticky[ $key ] ) && '' !== $sticky[ $key ] ) : ?> 130 value="<?php echo esc_attr( $sticky[ $key ] ); ?>" 131 <?php endif; ?> 126 132 /> 127 133 <label class="ranq-field__sublabel" for="<?php echo esc_attr( $base . '-' . $key ); ?>"> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-checkbox.php
r3681524 r3763733 86 86 $choices = $this->get_choices( $field ); 87 87 $base_id = $this->input_id( $field, $form_id ); 88 89 // After a failed send the visitor's ticks come back instead of the defaults. 90 $sticky = $this->sticky_value( $field, $form_id ); 88 91 ?> 89 92 <div class="ranq-field ranq-field--checkbox<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 112 115 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 113 116 value="<?php echo esc_attr( $choice['value'] ); ?>" 114 <?php checked( true, $ choice['default']); ?>117 <?php checked( true, $this->choice_on( $choice, $sticky ) ); ?> 115 118 /> 116 119 <label for="<?php echo esc_attr( $choice_id ); ?>"> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-email.php
r3681524 r3763733 66 66 67 67 /** 68 * What comes back into the box after a failed send. 69 * 70 * sanitize_email() throws away anything that is not a whole address, and the 71 * most common reason this very field fails is a half-typed address. Giving 72 * back an empty box would hide the typo the visitor needs to see to fix it. 73 * So what comes back is cleaned as plain text instead, escaped on printing 74 * like every other value, while the strict cleaning above still guards 75 * everything that gets stored or mailed. 76 * 77 * @param array $field The field settings. 78 * @param int $form_id The form id. 79 * 80 * @return mixed The sent text, or null when there is none. 81 */ 82 protected function sticky_value( $field, $form_id ) { 83 84 $raw = Ranq_Forms_Submission::posted_raw( $form_id, $this->field_key( $field ) ); 85 86 if ( null === $raw ) { 87 return null; 88 } 89 90 return sanitize_text_field( is_scalar( $raw ) ? (string) $raw : '' ); 91 } 92 93 /** 68 94 * Prints the field. 69 95 * … … 76 102 77 103 $describedby = $this->describedby( $field, $form_id ); 104 105 // After a failed send the visitor's address comes back instead of a blank box. 106 $sticky = $this->sticky_value( $field, $form_id ); 78 107 ?> 79 108 <div class="ranq-field ranq-field--email<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 86 115 autocomplete="email" 87 116 inputmode="email" 117 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 118 value="<?php echo esc_attr( $sticky ); ?>" 119 <?php endif; ?> 88 120 <?php if ( ! empty( $field['placeholder'] ) ) : ?> 89 121 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-imagechoice.php
r3681524 r3763733 155 155 $base_id = $this->input_id( $field, $form_id ); 156 156 $layout = isset( $field['picture_layout'] ) && 'list' === $field['picture_layout'] ? 'list' : 'grid'; 157 158 // After a failed send the visitor's pick comes back instead of the default. 159 $sticky = $this->sticky_value( $field, $form_id ); 157 160 ?> 158 161 <div class="ranq-field ranq-field--imagechoice ranq-field--pick-<?php echo esc_attr( $layout ); ?><?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 182 185 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 183 186 value="<?php echo esc_attr( $choice['value'] ); ?>" 184 <?php checked( true, $ choice['default']&& ! $choice['disabled'] ); ?>187 <?php checked( true, $this->choice_on( $choice, $sticky ) && ! $choice['disabled'] ); ?> 185 188 <?php disabled( true, $choice['disabled'] ); ?> 186 189 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-name.php
r3681524 r3763733 99 99 $describedby = $this->describedby( $field, $form_id ); 100 100 $base = $this->input_id( $field, $form_id ); 101 102 // After a failed send the visitor's name comes back instead of blank boxes. 103 $sticky = $this->sticky_value( $field, $form_id ); 101 104 ?> 102 105 <div class="ranq-field ranq-field--name<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 120 123 class="ranq-field__input" 121 124 autocomplete="<?php echo esc_attr( $part[1] ); ?>" 125 <?php if ( is_array( $sticky ) && isset( $sticky[ $key ] ) && '' !== $sticky[ $key ] ) : ?> 126 value="<?php echo esc_attr( $sticky[ $key ] ); ?>" 127 <?php endif; ?> 122 128 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 123 129 /> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-number.php
r3681524 r3763733 94 94 $has_min = isset( $field['min'] ) && '' !== $field['min']; 95 95 $has_max = isset( $field['max'] ) && '' !== $field['max']; 96 97 // After a failed send the visitor's number comes back instead of a blank box. 98 $sticky = $this->sticky_value( $field, $form_id ); 96 99 ?> 97 100 <div class="ranq-field ranq-field--number<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 103 106 class="ranq-field__input" 104 107 inputmode="decimal" 108 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 109 value="<?php echo esc_attr( $sticky ); ?>" 110 <?php endif; ?> 105 111 <?php if ( $has_min ) : ?> 106 112 min="<?php echo esc_attr( (string) $field['min'] ); ?>" -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-password.php
r3681524 r3763733 71 71 72 72 /** 73 * A password never comes back into the page, not even after a failed send. 74 * 75 * Every other field gives the visitor their text back, this one must not: a 76 * value printed into the HTML can end up in a cache, in a saved copy of the 77 * page, or on a shared screen. Typing it again is the smaller harm. 78 * 79 * @param array $field The field settings. 80 * @param int $form_id The form id. 81 * 82 * @return null 83 */ 84 protected function sticky_value( $field, $form_id ) { 85 86 unset( $field, $form_id ); 87 88 return null; 89 } 90 91 /** 73 92 * A password is NEVER shown in the entry or in the email. 74 93 * -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-phone.php
r3681524 r3763733 286 286 $name = $this->input_name( $field ); 287 287 $guess = $this->guess_country( $field ); 288 289 // After a failed send the visitor's country and number come back. sanitize() 290 // always fills the country in, so an empty one only means a fresh form. 291 $sticky = $this->sticky_value( $field, $form_id ); 292 $country = is_array( $sticky ) && ! empty( $sticky['country'] ) ? $sticky['country'] : $guess; 293 $number = is_array( $sticky ) && isset( $sticky['number'] ) 294 ? (string) $sticky['number'] 295 : ( empty( $field['default_value'] ) ? '' : (string) $field['default_value'] ); 288 296 ?> 289 297 <div class="ranq-field ranq-field--phone ranq-field--phone-smart<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 310 318 data-trunk="<?php echo esc_attr( $country['trunk'] ); ?>" 311 319 data-name="<?php echo esc_attr( $country['name'] ); ?>" 312 <?php selected( $code, $ guess); ?>320 <?php selected( $code, $country ); ?> 313 321 > 314 322 <?php echo esc_html( $code . ' +' . $country['dial'] . ' ' . $country['name'] ); ?> … … 326 334 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" 327 335 <?php endif; ?> 328 <?php if ( ! empty( $field['default_value'] )) : ?>329 value="<?php echo esc_attr( $ field['default_value']); ?>"336 <?php if ( '' !== $number ) : ?> 337 value="<?php echo esc_attr( $number ); ?>" 330 338 <?php endif; ?> 331 339 <?php if ( $describedby ) : ?> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-radio.php
r3681524 r3763733 66 66 $choices = $this->get_choices( $field ); 67 67 $base_id = $this->input_id( $field, $form_id ); 68 69 // After a failed send the visitor's pick comes back instead of the default. 70 $sticky = $this->sticky_value( $field, $form_id ); 68 71 ?> 69 72 <div class="ranq-field ranq-field--radio<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 92 95 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 93 96 value="<?php echo esc_attr( $choice['value'] ); ?>" 94 <?php checked( true, $ choice['default']); ?>97 <?php checked( true, $this->choice_on( $choice, $sticky ) ); ?> 95 98 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 96 99 /> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-ranking.php
r3681524 r3763733 73 73 $base_id = $this->input_id( $field, $form_id ); 74 74 $ukupno = count( $choices ); 75 76 // After a failed send the visitor's order comes back instead of the one 77 // the owner wrote the options in. 78 $sticky = $this->sticky_value( $field, $form_id ); 75 79 ?> 76 80 <div class="ranq-field ranq-field--ranking<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 112 116 class="ranq-rank__input" 113 117 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>[<?php echo esc_attr( $choice['value'] ); ?>]" 114 value="<?php echo esc_attr( (string) ( $index + 1 ) ); ?>"118 value="<?php echo esc_attr( (string) ( is_array( $sticky ) && isset( $sticky[ $choice['value'] ] ) ? (int) $sticky[ $choice['value'] ] : $index + 1 ) ); ?>" 115 119 min="1" 116 120 max="<?php echo esc_attr( (string) $ukupno ); ?>" -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-rating.php
r3681524 r3763733 110 110 $base = $this->input_id( $field, $form_id ); 111 111 $scale = $this->scale( $field ); 112 113 // After a failed send the visitor's stars come back instead of none. 114 $sticky = $this->sticky_value( $field, $form_id ); 112 115 ?> 113 116 <div class="ranq-field ranq-field--rating<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 130 133 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 131 134 value="<?php echo esc_attr( (string) $i ); ?>" 135 <?php checked( true, is_string( $sticky ) && (string) $i === $sticky ); ?> 132 136 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 133 137 /> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-select.php
r3681524 r3763733 72 72 $describedby = $this->describedby( $field, $form_id ); 73 73 $choices = $this->get_choices( $field ); 74 75 // After a failed send the visitor's pick comes back instead of the default. 76 $sticky = $this->sticky_value( $field, $form_id ); 74 77 ?> 75 78 <div class="ranq-field ranq-field--select<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 88 91 <option 89 92 value="<?php echo esc_attr( $choice['value'] ); ?>" 90 <?php selected( true, $ choice['default']); ?>93 <?php selected( true, $this->choice_on( $choice, $sticky ) ); ?> 91 94 > 92 95 <?php echo esc_html( $choice['label'] ); ?> -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-text.php
r3681524 r3763733 80 80 81 81 $describedby = $this->describedby( $field, $form_id ); 82 83 // After a failed send the visitor's text comes back instead of a blank box. 84 $sticky = $this->sticky_value( $field, $form_id ); 82 85 ?> 83 86 <div class="ranq-field ranq-field--text<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 88 91 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 89 92 class="ranq-field__input" 93 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 94 value="<?php echo esc_attr( $sticky ); ?>" 95 <?php endif; ?> 90 96 <?php if ( ! empty( $field['placeholder'] ) ) : ?> 91 97 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" -
2ranq-forms/tags/1.2.0/includes/fields/class-ranq-forms-field-textarea.php
r3681524 r3763733 102 102 $describedby = $this->describedby( $field, $form_id ); 103 103 $rows = isset( $field['rows'] ) ? absint( $field['rows'] ) : 5; 104 105 // After a failed send the visitor's text comes back. This is the field 106 // where losing it hurts the most: the message itself lives here. 107 $sticky = $this->sticky_value( $field, $form_id ); 104 108 ?> 105 109 <div class="ranq-field ranq-field--textarea<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 120 124 <?php endif; ?> 121 125 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 122 >< /textarea>126 ><?php echo is_string( $sticky ) ? esc_textarea( $sticky ) : ''; ?></textarea> 123 127 <?php $this->render_description( $field, $form_id ); ?> 124 128 </div> -
2ranq-forms/tags/1.2.0/readme.txt
r3688493 r3763733 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 93 93 Privacy policy: https://www.hcaptcha.com/privacy 94 94 95 **Cloudflare Turnstile** 96 97 Used for the same job as reCAPTCHA, usually without showing the visitor anything 98 to click. 99 100 The Turnstile script (https://challenges.cloudflare.com/turnstile/v0/api.js) is 101 loaded in the visitor's browser on every page that shows a protected form, which 102 lets Cloudflare see the visitor's IP address and browser. When the form is 103 submitted, the token and your secret key are sent from your server to 104 https://challenges.cloudflare.com/turnstile/v0/siteverify to be verified. The 105 answers typed into the form are never sent. 106 107 Service provided by Cloudflare, Inc. Terms of service: 108 https://www.cloudflare.com/terms/ 109 Privacy policy: https://www.cloudflare.com/privacypolicy/ 110 95 111 **Mailchimp** 96 112 … … 157 173 158 174 No. A hidden trap field and a rate limit are always on and need nothing from 159 outside. Google reCAPTCHA and hCaptcha are there if you want them, and both are160 off until you enter your own keys.175 outside. Google reCAPTCHA, hCaptcha and Cloudflare Turnstile are there if you 176 want them, and all three are off until you enter your own keys. 161 177 162 178 = Where are uploaded files kept? = … … 205 221 206 222 == Changelog == 223 224 = 1.2.0 = 225 * A send from a cached page no longer disappears. The security token in a form dies after a day, while a cache plugin can serve the same page for a week, and a visitor sending from such a page was thrown away without a word and without their text. The token is now only asked of logged-in visitors, where it does its real CSRF job. A public form is guarded by the hidden trap field and the rate limit, which do not age with the page. Contact Form 7 and Gravity Forms handle a public form the same way. 226 * No rejected send is silent any more. When a check says no, the visitor is told on the page, and every field gives them back what they typed: text, choices, ticks, stars, ranking order, name, address, phone. A password is never printed back, and a browser does not allow a file to be put back into a file box. 227 * Cloudflare Turnstile is back as the third spam check, next to Google reCAPTCHA and hCaptcha. It sat out the first release over a false positive in the directory's machine check, not over anything in how it works. Off until you enter your own keys, like the other two. 207 228 208 229 = 1.1.0 = … … 235 256 == Upgrade Notice == 236 257 258 = 1.2.0 = 259 Recommended for every site with page caching. A visitor sending from a cached page is no longer silently thrown away, every rejected send now shows a message and keeps the typed values, and Cloudflare Turnstile joins reCAPTCHA and hCaptcha. 260 237 261 = 1.1.0 = 238 262 Recommended. Notifications now arrive as a card with a frame colour you choose, the link opens the entry itself, and answers to a dropdown, a radio button or a checkbox read as the visitor saw them. -
2ranq-forms/trunk/2ranq-forms.php
r3688493 r3763733 4 4 * Plugin URI: https://2ranq.com/plugins/2ranq-forms/ 5 5 * Description: Form builder with conditional logic, multi-step forms and file uploads. Every feature free, nothing locked. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 6.6 8 8 * Requires PHP: 7.4 … … 33 33 */ 34 34 35 define( 'RANQ_FORMS_VERSION', '1. 1.0' );35 define( 'RANQ_FORMS_VERSION', '1.2.0' ); 36 36 define( 'RANQ_FORMS_FILE', __FILE__ ); 37 37 define( 'RANQ_FORMS_DIR', plugin_dir_path( __FILE__ ) ); -
2ranq-forms/trunk/includes/class-ranq-forms-spam.php
r3681524 r3763733 3 3 * Spam protection, with the keys of outside services. 4 4 * 5 * Google reCAPTCHA (v2 checkbox, v2 invisible, v3) and hCaptcha, all unlocked. 6 * The owner enters the keys once, and every form can use them. 7 * 8 * Cloudflare Turnstile WORKED and was taken out of the first release, because 9 * the official Plugin Check flags every `cloudflare.com` and rejects the 10 * submission by machine. The whole removed code and the procedure for putting it 11 * back are in `13-TURNSTILE-VRACANJE.md`. 5 * Google reCAPTCHA (v2 checkbox, v2 invisible, v3), hCaptcha and Cloudflare 6 * Turnstile, all unlocked. The owner enters the keys once, and every form can 7 * use them. 8 * 9 * Turnstile sat out the first release: the Plugin Check "offloading" sniff 10 * flags every `cloudflare.com` address because Cloudflare also serves other 11 * people's files, and an error of that level rejects a NEW submission by 12 * machine. Turnstile is not file offloading, it is a visitor check, the same 13 * as the two services above it that the sniff does not flag. It came back in 14 * 1.2.0, with the sniff told so on each of the two addresses. 12 15 * 13 16 * The check runs ON THE SERVER. Without that the protection is worth nothing, … … 81 84 'script' => 'https://js.hcaptcha.com/1/api.js', 82 85 ), 86 'turnstile' => array( 87 'name' => __( 'Cloudflare Turnstile', '2ranq-forms' ), 88 'note' => __( 'Free, quiet, and usually invisible to the visitor. The easiest of the three.', '2ranq-forms' ), 89 // The dashboard address under the button the owner presses to go and 90 // make their keys, the same as the two services above have. Nothing 91 // is loaded from it, so it is not offloading either. 92 'link' => 'https://dash.cloudflare.com/?to=/:account/turnstile', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 93 'link_text' => __( 'Open Turnstile in Cloudflare', '2ranq-forms' ), 94 'steps' => array( 95 __( 'Make a free Cloudflare account. Your site does not have to use Cloudflare for anything else.', '2ranq-forms' ), 96 __( 'Open Turnstile, press Add widget, type your domain, and leave the mode on Managed.', '2ranq-forms' ), 97 __( 'Copy the Site Key and the Secret Key into the two boxes below.', '2ranq-forms' ), 98 ), 99 // The two addresses below are the visitor check itself, not content 100 // offloading: nothing of the site is served from them, they only 101 // answer whether the visitor passed. The sniff cannot tell the two 102 // apart because both live on cloudflare.com, so it is told here. 103 'verify' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 104 'script' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent 105 ), 83 106 ); 84 107 } … … 221 244 } 222 245 223 $klasa = 'hcaptcha' === $s['provider'] ? 'h-captcha' : 'g-recaptcha';246 $klasa = 'hcaptcha' === $s['provider'] ? 'h-captcha' : ( 'turnstile' === $s['provider'] ? 'cf-turnstile' : 'g-recaptcha' ); 224 247 $extra = ''; 225 248 … … 304 327 if ( '' === $odgovor ) { 305 328 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the same. 329 $odgovor = isset( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : ''; 330 } 331 332 if ( '' === $odgovor ) { 333 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- the same. 306 334 $odgovor = isset( $_POST['ranq_spam_token'] ) ? sanitize_text_field( wp_unslash( $_POST['ranq_spam_token'] ) ) : ''; 307 335 } -
2ranq-forms/trunk/includes/class-ranq-forms-submission.php
r3681524 r3763733 44 44 } 45 45 46 if ( ! isset( $_POST['ranq_forms_id'] , $_POST['ranq_forms_nonce']) ) {46 if ( ! isset( $_POST['ranq_forms_id'] ) ) { 47 47 return; 48 48 } … … 51 51 52 52 if ( ! $form_id ) { 53 return;54 }55 56 $nonce = sanitize_text_field( wp_unslash( $_POST['ranq_forms_nonce'] ) );57 58 if ( ! wp_verify_nonce( $nonce, 'ranq_forms_submit_' . $form_id ) ) {59 return;60 }61 62 // The bot trap. The field sits in the form, moved off the screen, and a person63 // neither sees it nor can fill it in with the keyboard. A robot that fills in64 // everything it finds fills that in too. It is rejected quietly, with no65 // message, because a message tells the robot where the mistake is.66 if ( ! empty( $_POST['ranq_hp'] ) ) {67 53 return; 68 54 } … … 72 58 // swallow the error and the visitor would stare at a blank form with nothing. 73 59 self::$submitted = $form_id; 60 61 // The nonce is only asked of a logged-in visitor, because only there it does 62 // its real job: it stops another site from riding that visitor's session. A 63 // guest has no session to ride, so for a guest the nonce protected nothing a 64 // robot could not get around by fetching a fresh page, and it did real harm: 65 // a cache plugin happily serves the same page for a week while the nonce in 66 // it dies after a day, so a visitor sending from a cached page was thrown 67 // away without a word. Contact Form 7 and Gravity Forms treat a public form 68 // the same way. What actually guards a public form are the bot trap and the 69 // rate limit below, which do not age with the page. 70 if ( is_user_logged_in() ) { 71 72 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the nonce is only compared, never stored or printed. 73 $nonce = isset( $_POST['ranq_forms_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ranq_forms_nonce'] ) ) : ''; 74 75 if ( ! wp_verify_nonce( $nonce, 'ranq_forms_submit_' . $form_id ) ) { 76 77 // Never silently. The page the visitor sent from had simply been open, 78 // or cached, for longer than the nonce lives. They get told, and the 79 // form below gives them their typed values back, so nothing is lost. 80 self::$errors = array( '_form' => __( 'This page had been open for too long. Please send the form again.', '2ranq-forms' ) ); 81 82 return; 83 } 84 } 85 86 // The bot trap. The field sits in the form, moved off the screen, and a person 87 // neither sees it nor can fill it in with the keyboard. A robot that fills in 88 // everything it finds fills that in too. The message is deliberately vague, it 89 // does not say which check failed, so a robot learns nothing from it. But it 90 // is no longer silent, because a browser extension that autofills every field 91 // it finds can trip the trap for a real person. That person gets their text 92 // back, the trap field renders empty again, and the second send goes through. 93 if ( ! empty( $_POST['ranq_hp'] ) ) { 94 self::$errors = array( '_form' => __( 'The form could not be sent. Please try again.', '2ranq-forms' ) ); 95 96 return; 97 } 74 98 75 99 // The rate limit. Without this one person can fill the database and, through … … 286 310 287 311 return self::$sent && self::$submitted === (int) $form_id; 312 } 313 314 /** 315 * What the visitor sent for one field, raw, or null. 316 * 317 * It only answers while THIS form's send has just failed, so the fields can 318 * give the visitor their typed values back instead of a blank form. Any other 319 * time, for any other form on the page, and after a send that went through, 320 * it stays quiet and the field falls back to its default. The value comes 321 * back raw on purpose: only the field's own class knows how to clean it, so 322 * cleaning happens there, never here. 323 * 324 * @param int $form_id The form id. 325 * @param string $key The field key. 326 * 327 * @return mixed The raw sent value, or null when there is nothing to give back. 328 */ 329 public static function posted_raw( $form_id, $key ) { 330 331 if ( self::$sent || self::$submitted !== (int) $form_id ) { 332 return null; 333 } 334 335 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- read only to hand the visitor's text back to the field, which cleans it with its own sanitize() before printing. 336 $raw = isset( $_POST['ranq_fields'] ) && is_array( $_POST['ranq_fields'] ) ? wp_unslash( $_POST['ranq_fields'] ) : array(); 337 338 return isset( $raw[ $key ] ) ? $raw[ $key ] : null; 288 339 } 289 340 -
2ranq-forms/trunk/includes/fields/abstract-ranq-forms-field-choices.php
r3688493 r3763733 107 107 108 108 /** 109 * Whether one choice renders as picked. 110 * 111 * While the form is fresh the owner's defaults decide. The moment a send has 112 * failed, what the visitor picked wins, even when they picked nothing, because 113 * putting the defaults back would quietly change their answer under them. 114 * 115 * @param array $choice One choice, as get_choices() returns it. 116 * @param mixed $sticky What sticky_value() gave for the whole field. 117 * 118 * @return bool 119 */ 120 protected function choice_on( $choice, $sticky ) { 121 122 if ( null === $sticky ) { 123 return ! empty( $choice['default'] ); 124 } 125 126 $picked = array_map( 'strval', array_filter( is_array( $sticky ) ? $sticky : array( $sticky ), 'is_scalar' ) ); 127 128 return in_array( (string) $choice['value'], $picked, true ); 129 } 130 131 /** 109 132 * The value prepared for display, as the label the visitor actually saw. 110 133 * -
2ranq-forms/trunk/includes/fields/abstract-ranq-forms-field-input.php
r3681524 r3763733 77 77 $mode = $this->input_mode(); 78 78 $auto = $this->autocomplete(); 79 80 // After a failed send the visitor's own text comes back, otherwise the 81 // owner's default. The two never mix: a failed send with an emptied field 82 // stays empty, because the default would change the answer under them. 83 $sticky = $this->sticky_value( $field, $form_id ); 84 $value = is_string( $sticky ) 85 ? $sticky 86 : ( empty( $field['default_value'] ) ? '' : (string) $field['default_value'] ); 79 87 ?> 80 88 <div class="ranq-field ranq-field--<?php echo esc_attr( $this->get_type() ); ?><?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 94 102 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" 95 103 <?php endif; ?> 96 <?php if ( ! empty( $field['default_value'] )) : ?>97 value="<?php echo esc_attr( $ field['default_value']); ?>"104 <?php if ( '' !== $value ) : ?> 105 value="<?php echo esc_attr( $value ); ?>" 98 106 <?php endif; ?> 99 107 <?php if ( $describedby ) : ?> -
2ranq-forms/trunk/includes/fields/abstract-ranq-forms-field.php
r3681524 r3763733 329 329 330 330 /** 331 * The value the field shows when the form comes back after a failed send. 332 * 333 * A send can fail long after the visitor wrote their text: a required field 334 * was missed, the spam check said no, the page had been cached for a week. A 335 * blank form at that moment throws the whole message away, and nobody types 336 * it twice. So every field that can, gives the text back. The value is 337 * cleaned by the field's own sanitize(), because what came in is as raw as 338 * any other sent value. Null means there is nothing to give back, and the 339 * field falls back to its default. A password and a file stay out of this on 340 * purpose: a password is never printed back into a page, and a browser does 341 * not allow a file input to be prefilled at all. 342 * 343 * @param array $field The field settings. 344 * @param int $form_id The form id. 345 * 346 * @return mixed The cleaned sent value, or null when there is none. 347 */ 348 protected function sticky_value( $field, $form_id ) { 349 350 $raw = Ranq_Forms_Submission::posted_raw( $form_id, $this->field_key( $field ) ); 351 352 if ( null === $raw ) { 353 return null; 354 } 355 356 return $this->sanitize( $raw, $field ); 357 } 358 359 /** 331 360 * The element id in the HTML, has to be unique per form. 332 361 * -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-address.php
r3681524 r3763733 103 103 $describedby = $this->describedby( $field, $form_id ); 104 104 $base = $this->input_id( $field, $form_id ); 105 106 // After a failed send the visitor's address comes back instead of blank boxes. 107 $sticky = $this->sticky_value( $field, $form_id ); 105 108 ?> 106 109 <div class="ranq-field ranq-field--address<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 124 127 class="ranq-field__input" 125 128 autocomplete="<?php echo esc_attr( $part[1] ); ?>" 129 <?php if ( is_array( $sticky ) && isset( $sticky[ $key ] ) && '' !== $sticky[ $key ] ) : ?> 130 value="<?php echo esc_attr( $sticky[ $key ] ); ?>" 131 <?php endif; ?> 126 132 /> 127 133 <label class="ranq-field__sublabel" for="<?php echo esc_attr( $base . '-' . $key ); ?>"> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-checkbox.php
r3681524 r3763733 86 86 $choices = $this->get_choices( $field ); 87 87 $base_id = $this->input_id( $field, $form_id ); 88 89 // After a failed send the visitor's ticks come back instead of the defaults. 90 $sticky = $this->sticky_value( $field, $form_id ); 88 91 ?> 89 92 <div class="ranq-field ranq-field--checkbox<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 112 115 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 113 116 value="<?php echo esc_attr( $choice['value'] ); ?>" 114 <?php checked( true, $ choice['default']); ?>117 <?php checked( true, $this->choice_on( $choice, $sticky ) ); ?> 115 118 /> 116 119 <label for="<?php echo esc_attr( $choice_id ); ?>"> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-email.php
r3681524 r3763733 66 66 67 67 /** 68 * What comes back into the box after a failed send. 69 * 70 * sanitize_email() throws away anything that is not a whole address, and the 71 * most common reason this very field fails is a half-typed address. Giving 72 * back an empty box would hide the typo the visitor needs to see to fix it. 73 * So what comes back is cleaned as plain text instead, escaped on printing 74 * like every other value, while the strict cleaning above still guards 75 * everything that gets stored or mailed. 76 * 77 * @param array $field The field settings. 78 * @param int $form_id The form id. 79 * 80 * @return mixed The sent text, or null when there is none. 81 */ 82 protected function sticky_value( $field, $form_id ) { 83 84 $raw = Ranq_Forms_Submission::posted_raw( $form_id, $this->field_key( $field ) ); 85 86 if ( null === $raw ) { 87 return null; 88 } 89 90 return sanitize_text_field( is_scalar( $raw ) ? (string) $raw : '' ); 91 } 92 93 /** 68 94 * Prints the field. 69 95 * … … 76 102 77 103 $describedby = $this->describedby( $field, $form_id ); 104 105 // After a failed send the visitor's address comes back instead of a blank box. 106 $sticky = $this->sticky_value( $field, $form_id ); 78 107 ?> 79 108 <div class="ranq-field ranq-field--email<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 86 115 autocomplete="email" 87 116 inputmode="email" 117 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 118 value="<?php echo esc_attr( $sticky ); ?>" 119 <?php endif; ?> 88 120 <?php if ( ! empty( $field['placeholder'] ) ) : ?> 89 121 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-imagechoice.php
r3681524 r3763733 155 155 $base_id = $this->input_id( $field, $form_id ); 156 156 $layout = isset( $field['picture_layout'] ) && 'list' === $field['picture_layout'] ? 'list' : 'grid'; 157 158 // After a failed send the visitor's pick comes back instead of the default. 159 $sticky = $this->sticky_value( $field, $form_id ); 157 160 ?> 158 161 <div class="ranq-field ranq-field--imagechoice ranq-field--pick-<?php echo esc_attr( $layout ); ?><?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 182 185 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 183 186 value="<?php echo esc_attr( $choice['value'] ); ?>" 184 <?php checked( true, $ choice['default']&& ! $choice['disabled'] ); ?>187 <?php checked( true, $this->choice_on( $choice, $sticky ) && ! $choice['disabled'] ); ?> 185 188 <?php disabled( true, $choice['disabled'] ); ?> 186 189 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-name.php
r3681524 r3763733 99 99 $describedby = $this->describedby( $field, $form_id ); 100 100 $base = $this->input_id( $field, $form_id ); 101 102 // After a failed send the visitor's name comes back instead of blank boxes. 103 $sticky = $this->sticky_value( $field, $form_id ); 101 104 ?> 102 105 <div class="ranq-field ranq-field--name<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 120 123 class="ranq-field__input" 121 124 autocomplete="<?php echo esc_attr( $part[1] ); ?>" 125 <?php if ( is_array( $sticky ) && isset( $sticky[ $key ] ) && '' !== $sticky[ $key ] ) : ?> 126 value="<?php echo esc_attr( $sticky[ $key ] ); ?>" 127 <?php endif; ?> 122 128 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 123 129 /> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-number.php
r3681524 r3763733 94 94 $has_min = isset( $field['min'] ) && '' !== $field['min']; 95 95 $has_max = isset( $field['max'] ) && '' !== $field['max']; 96 97 // After a failed send the visitor's number comes back instead of a blank box. 98 $sticky = $this->sticky_value( $field, $form_id ); 96 99 ?> 97 100 <div class="ranq-field ranq-field--number<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 103 106 class="ranq-field__input" 104 107 inputmode="decimal" 108 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 109 value="<?php echo esc_attr( $sticky ); ?>" 110 <?php endif; ?> 105 111 <?php if ( $has_min ) : ?> 106 112 min="<?php echo esc_attr( (string) $field['min'] ); ?>" -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-password.php
r3681524 r3763733 71 71 72 72 /** 73 * A password never comes back into the page, not even after a failed send. 74 * 75 * Every other field gives the visitor their text back, this one must not: a 76 * value printed into the HTML can end up in a cache, in a saved copy of the 77 * page, or on a shared screen. Typing it again is the smaller harm. 78 * 79 * @param array $field The field settings. 80 * @param int $form_id The form id. 81 * 82 * @return null 83 */ 84 protected function sticky_value( $field, $form_id ) { 85 86 unset( $field, $form_id ); 87 88 return null; 89 } 90 91 /** 73 92 * A password is NEVER shown in the entry or in the email. 74 93 * -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-phone.php
r3681524 r3763733 286 286 $name = $this->input_name( $field ); 287 287 $guess = $this->guess_country( $field ); 288 289 // After a failed send the visitor's country and number come back. sanitize() 290 // always fills the country in, so an empty one only means a fresh form. 291 $sticky = $this->sticky_value( $field, $form_id ); 292 $country = is_array( $sticky ) && ! empty( $sticky['country'] ) ? $sticky['country'] : $guess; 293 $number = is_array( $sticky ) && isset( $sticky['number'] ) 294 ? (string) $sticky['number'] 295 : ( empty( $field['default_value'] ) ? '' : (string) $field['default_value'] ); 288 296 ?> 289 297 <div class="ranq-field ranq-field--phone ranq-field--phone-smart<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 310 318 data-trunk="<?php echo esc_attr( $country['trunk'] ); ?>" 311 319 data-name="<?php echo esc_attr( $country['name'] ); ?>" 312 <?php selected( $code, $ guess); ?>320 <?php selected( $code, $country ); ?> 313 321 > 314 322 <?php echo esc_html( $code . ' +' . $country['dial'] . ' ' . $country['name'] ); ?> … … 326 334 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" 327 335 <?php endif; ?> 328 <?php if ( ! empty( $field['default_value'] )) : ?>329 value="<?php echo esc_attr( $ field['default_value']); ?>"336 <?php if ( '' !== $number ) : ?> 337 value="<?php echo esc_attr( $number ); ?>" 330 338 <?php endif; ?> 331 339 <?php if ( $describedby ) : ?> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-radio.php
r3681524 r3763733 66 66 $choices = $this->get_choices( $field ); 67 67 $base_id = $this->input_id( $field, $form_id ); 68 69 // After a failed send the visitor's pick comes back instead of the default. 70 $sticky = $this->sticky_value( $field, $form_id ); 68 71 ?> 69 72 <div class="ranq-field ranq-field--radio<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 92 95 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 93 96 value="<?php echo esc_attr( $choice['value'] ); ?>" 94 <?php checked( true, $ choice['default']); ?>97 <?php checked( true, $this->choice_on( $choice, $sticky ) ); ?> 95 98 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 96 99 /> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-ranking.php
r3681524 r3763733 73 73 $base_id = $this->input_id( $field, $form_id ); 74 74 $ukupno = count( $choices ); 75 76 // After a failed send the visitor's order comes back instead of the one 77 // the owner wrote the options in. 78 $sticky = $this->sticky_value( $field, $form_id ); 75 79 ?> 76 80 <div class="ranq-field ranq-field--ranking<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 112 116 class="ranq-rank__input" 113 117 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>[<?php echo esc_attr( $choice['value'] ); ?>]" 114 value="<?php echo esc_attr( (string) ( $index + 1 ) ); ?>"118 value="<?php echo esc_attr( (string) ( is_array( $sticky ) && isset( $sticky[ $choice['value'] ] ) ? (int) $sticky[ $choice['value'] ] : $index + 1 ) ); ?>" 115 119 min="1" 116 120 max="<?php echo esc_attr( (string) $ukupno ); ?>" -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-rating.php
r3681524 r3763733 110 110 $base = $this->input_id( $field, $form_id ); 111 111 $scale = $this->scale( $field ); 112 113 // After a failed send the visitor's stars come back instead of none. 114 $sticky = $this->sticky_value( $field, $form_id ); 112 115 ?> 113 116 <div class="ranq-field ranq-field--rating<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 130 133 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 131 134 value="<?php echo esc_attr( (string) $i ); ?>" 135 <?php checked( true, is_string( $sticky ) && (string) $i === $sticky ); ?> 132 136 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 133 137 /> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-select.php
r3681524 r3763733 72 72 $describedby = $this->describedby( $field, $form_id ); 73 73 $choices = $this->get_choices( $field ); 74 75 // After a failed send the visitor's pick comes back instead of the default. 76 $sticky = $this->sticky_value( $field, $form_id ); 74 77 ?> 75 78 <div class="ranq-field ranq-field--select<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 88 91 <option 89 92 value="<?php echo esc_attr( $choice['value'] ); ?>" 90 <?php selected( true, $ choice['default']); ?>93 <?php selected( true, $this->choice_on( $choice, $sticky ) ); ?> 91 94 > 92 95 <?php echo esc_html( $choice['label'] ); ?> -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-text.php
r3681524 r3763733 80 80 81 81 $describedby = $this->describedby( $field, $form_id ); 82 83 // After a failed send the visitor's text comes back instead of a blank box. 84 $sticky = $this->sticky_value( $field, $form_id ); 82 85 ?> 83 86 <div class="ranq-field ranq-field--text<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 88 91 name="<?php echo esc_attr( $this->input_name( $field ) ); ?>" 89 92 class="ranq-field__input" 93 <?php if ( is_string( $sticky ) && '' !== $sticky ) : ?> 94 value="<?php echo esc_attr( $sticky ); ?>" 95 <?php endif; ?> 90 96 <?php if ( ! empty( $field['placeholder'] ) ) : ?> 91 97 placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" -
2ranq-forms/trunk/includes/fields/class-ranq-forms-field-textarea.php
r3681524 r3763733 102 102 $describedby = $this->describedby( $field, $form_id ); 103 103 $rows = isset( $field['rows'] ) ? absint( $field['rows'] ) : 5; 104 105 // After a failed send the visitor's text comes back. This is the field 106 // where losing it hurts the most: the message itself lives here. 107 $sticky = $this->sticky_value( $field, $form_id ); 104 108 ?> 105 109 <div class="ranq-field ranq-field--textarea<?php echo esc_attr( $this->extra_classes( $field ) ); ?>"<?php echo $this->wrapper_data( $field ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped in wrapper_data().?>> … … 120 124 <?php endif; ?> 121 125 <?php echo empty( $field['required'] ) ? '' : 'required aria-required="true"'; ?> 122 >< /textarea>126 ><?php echo is_string( $sticky ) ? esc_textarea( $sticky ) : ''; ?></textarea> 123 127 <?php $this->render_description( $field, $form_id ); ?> 124 128 </div> -
2ranq-forms/trunk/readme.txt
r3688493 r3763733 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 93 93 Privacy policy: https://www.hcaptcha.com/privacy 94 94 95 **Cloudflare Turnstile** 96 97 Used for the same job as reCAPTCHA, usually without showing the visitor anything 98 to click. 99 100 The Turnstile script (https://challenges.cloudflare.com/turnstile/v0/api.js) is 101 loaded in the visitor's browser on every page that shows a protected form, which 102 lets Cloudflare see the visitor's IP address and browser. When the form is 103 submitted, the token and your secret key are sent from your server to 104 https://challenges.cloudflare.com/turnstile/v0/siteverify to be verified. The 105 answers typed into the form are never sent. 106 107 Service provided by Cloudflare, Inc. Terms of service: 108 https://www.cloudflare.com/terms/ 109 Privacy policy: https://www.cloudflare.com/privacypolicy/ 110 95 111 **Mailchimp** 96 112 … … 157 173 158 174 No. A hidden trap field and a rate limit are always on and need nothing from 159 outside. Google reCAPTCHA and hCaptcha are there if you want them, and both are160 off until you enter your own keys.175 outside. Google reCAPTCHA, hCaptcha and Cloudflare Turnstile are there if you 176 want them, and all three are off until you enter your own keys. 161 177 162 178 = Where are uploaded files kept? = … … 205 221 206 222 == Changelog == 223 224 = 1.2.0 = 225 * A send from a cached page no longer disappears. The security token in a form dies after a day, while a cache plugin can serve the same page for a week, and a visitor sending from such a page was thrown away without a word and without their text. The token is now only asked of logged-in visitors, where it does its real CSRF job. A public form is guarded by the hidden trap field and the rate limit, which do not age with the page. Contact Form 7 and Gravity Forms handle a public form the same way. 226 * No rejected send is silent any more. When a check says no, the visitor is told on the page, and every field gives them back what they typed: text, choices, ticks, stars, ranking order, name, address, phone. A password is never printed back, and a browser does not allow a file to be put back into a file box. 227 * Cloudflare Turnstile is back as the third spam check, next to Google reCAPTCHA and hCaptcha. It sat out the first release over a false positive in the directory's machine check, not over anything in how it works. Off until you enter your own keys, like the other two. 207 228 208 229 = 1.1.0 = … … 235 256 == Upgrade Notice == 236 257 258 = 1.2.0 = 259 Recommended for every site with page caching. A visitor sending from a cached page is no longer silently thrown away, every rejected send now shows a message and keeps the typed values, and Cloudflare Turnstile joins reCAPTCHA and hCaptcha. 260 237 261 = 1.1.0 = 238 262 Recommended. Notifications now arrive as a card with a frame colour you choose, the link opens the entry itself, and answers to a dropdown, a radio button or a checkbox read as the visitor saw them.
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)