PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.3
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.3
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / FrmAntiSpam.php

FrmAntiSpam.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.3, at classes/models/FrmAntiSpam.php

347 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 /**
7 * Class FrmAntiSpam.
8 *
9 * This token class generates tokens that are used in our Anti-Spam checking.
10 *
11 * @since 4.11
12 */
13 class FrmAntiSpam extends FrmValidate {
14
15 /**
16 * @return string
17 */
18 protected function get_option_key() {
19 return 'antispam';
20 }
21
22 /**
23 * @param int $form_id
24 *
25 * @return void
26 */
27 public static function maybe_init( $form_id ) {
28 $antispam = new self( $form_id );
29 if ( $antispam->run_antispam() ) {
30 $antispam->init();
31 }
32 }
33
34 /**
35 * Initialise the actions for the Anti-spam.
36 *
37 * @since 4.11
38 *
39 * @return void
40 */
41 public function init() {
42 add_filter( 'frm_form_attributes', array( $this, 'add_token_to_form' ), 10, 1 );
43 add_filter( 'frm_form_div_attributes', array( $this, 'add_token_to_form' ), 10, 1 );
44 }
45
46 /**
47 * Return a valid token.
48 *
49 * @since 4.11
50 *
51 * @param mixed $current True to use current time, otherwise a timestamp string.
52 *
53 * @return string Token.
54 */
55 private function get( $current = true ) {
56 // If $current was not passed, or it is true, we use the current timestamp.
57 // If $current was passed in as a string, we'll use that passed in timestamp.
58 if ( $current !== true ) {
59 $time = $current;
60 } else {
61 $time = time();
62 }
63
64 // Format the timestamp to be less exact, as we want to deal in days.
65 // June 19th, 2020 would get formatted as: 1906202017125.
66 // Day of the month, month number, year, day number of the year, week number of the year.
67 $token_date = gmdate( 'dmYzW', $time );
68
69 // Combine our token date and our token salt, and md5 it.
70 $form_token_string = md5( $token_date . $this->get_antispam_secret_key() );
71
72 return $form_token_string;
73 }
74
75 private function get_antispam_secret_key() {
76 $secret_key = get_option( 'frm_antispam_secret_key' );
77
78 // If we already have the secret, send it back.
79 if ( false !== $secret_key ) {
80 return base64_decode( $secret_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
81 }
82
83 // We don't have a secret, so let's generate one.
84 $secret_key = is_callable( 'sodium_crypto_secretbox_keygen' ) ? sodium_crypto_secretbox_keygen() : wp_generate_password( 32, true, true );
85 add_option( 'frm_antispam_secret_key', base64_encode( $secret_key ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
86
87 return $secret_key;
88 }
89
90 /**
91 * Generate the array of valid tokens to check for. These include two days
92 * before the current date to account for long cache times.
93 *
94 * These two filters are available if a user wants to extend the times.
95 * 'frm_form_token_check_before_today'
96 * 'frm_form_token_check_after_today'
97 *
98 * @since 4.11
99 *
100 * @return array Array of all valid tokens to check against.
101 */
102 private function get_valid_tokens() {
103 $current_date = time();
104
105 // Create our array of times to check before today. A user with a longer
106 // cache time can extend this. A user with a shorter cache time can remove times.
107 $valid_token_times_before = apply_filters(
108 'frm_form_token_check_before_today',
109 array(
110 ( 2 * DAY_IN_SECONDS ), // Two days ago.
111 ( 1 * DAY_IN_SECONDS ), // One day ago.
112 )
113 );
114
115 // Mostly to catch edge cases like the form page loading and submitting on two different days.
116 // This probably won't be filtered by users too much, but they could extend it.
117 $valid_token_times_after = apply_filters(
118 'frm_form_token_check_after_today',
119 array(
120 ( 45 * MINUTE_IN_SECONDS ), // Add in 45 minutes past today to catch some midnight edge cases.
121 )
122 );
123
124 // Built up our valid tokens.
125 $valid_tokens = array();
126
127 // Add in all the previous times we check.
128 foreach ( $valid_token_times_before as $time ) {
129 $valid_tokens[] = $this->get( $current_date - $time );
130 }
131
132 // Add in our current date.
133 $valid_tokens[] = $this->get( $current_date );
134
135 // Add in the times after our check.
136 foreach ( $valid_token_times_after as $time ) {
137 $valid_tokens[] = $this->get( $current_date + $time );
138 }
139
140 return $valid_tokens;
141 }
142
143 /**
144 * Check if the given token is valid or not.
145 *
146 * Tokens are valid for some period of time (see frm_token_validity_in_hours
147 * and frm_token_validity_in_days to extend the validation period).
148 * By default tokens are valid for day.
149 *
150 * @since 4.11
151 *
152 * @param string $token Token to validate.
153 *
154 * @return bool Whether the token is valid or not.
155 */
156 private function verify( $token ) {
157 // Check to see if our token is inside of the valid tokens.
158 return in_array( $token, $this->get_valid_tokens(), true );
159 }
160
161 /**
162 * Add the token field to the form.
163 *
164 * @since 4.11
165 *
166 * @param string $attributes
167 *
168 * @return string
169 */
170 public function add_token_to_form( $attributes ) {
171 $attributes .= ' data-token="' . esc_attr( $this->get() ) . '"';
172 return $attributes;
173 }
174
175 /**
176 * @param int $form_id
177 *
178 * @return void
179 */
180 public static function maybe_echo_token( $form_id ) {
181 $antispam = new self( $form_id );
182 if ( $antispam->run_antispam() ) {
183 echo 'data-token="' . esc_attr( $antispam->get() ) . '"';
184 }
185 }
186
187 /**
188 * @return bool
189 */
190 public function run_antispam() {
191 return $this->is_option_on() && apply_filters( 'frm_run_antispam', true, $this->form_id );
192 }
193
194 /**
195 * Validate Anti-spam if enabled.
196 *
197 * @since 4.11
198 *
199 * @return bool|string True or a string with the error.
200 */
201 public function validate() {
202 if ( ! $this->run_antispam() ) {
203 return true;
204 }
205
206 $token = FrmAppHelper::get_param( 'antispam_token', '', 'post', 'sanitize_text_field' );
207
208 // If the antispam setting is enabled and we don't have a token, bail.
209 if ( ! $token ) {
210 if ( FrmAppHelper::is_admin_page( 'formidable-entries' ) ) {
211 // add an exception for the entries page.
212 return true;
213 }
214 return $this->process_antispam_filter( $this->get_missing_token_message() );
215 }
216
217 // Verify the token.
218 if ( ! $this->verify( $token ) ) {
219 return $this->process_antispam_filter( $this->get_invalid_token_message() );
220 }
221
222 return $this->process_antispam_filter( true );
223 }
224
225 /**
226 * @return bool True if saving a draft.
227 */
228 private function is_saving_a_draft() {
229 global $frm_vars;
230 if ( empty( $frm_vars['form_params'] ) ) {
231 return false;
232 }
233 $form_params = $frm_vars['form_params'];
234 if ( ! isset( $form_params[ $this->form_id ] ) ) {
235 return false;
236 }
237 $this_form_params = $form_params[ $this->form_id ];
238 return ! empty( $this_form_params['action'] ) && 'update' === $this_form_params['action'];
239 }
240
241 /**
242 * Helper to run our filter on all the responses for the antispam checks.
243 *
244 * @since 4.11
245 *
246 * @param bool|string $is_valid Is valid entry or not.
247 *
248 * @return bool|string Is valid or message.
249 */
250 private function process_antispam_filter( $is_valid ) {
251 return apply_filters( 'frm_process_antispam', $is_valid );
252 }
253
254 /**
255 * Helper to get the missing token message.
256 *
257 * @since 4.11
258 *
259 * @return string missing token message.
260 */
261 private function get_missing_token_message() {
262 return esc_html__( 'This page isn\'t loading JavaScript properly, and the form will not be able to submit.', 'formidable' ) . $this->maybe_get_support_text();
263 }
264
265 /**
266 * Helper to get the invalid token message.
267 *
268 * @since 4.11
269 *
270 * @return string Invalid token message.
271 */
272 private function get_invalid_token_message() {
273 return esc_html__( 'Form token is invalid. Please refresh the page.', 'formidable' ) . $this->maybe_get_support_text();
274 }
275
276 /**
277 * If a user is a super admin, add a support link to the message.
278 *
279 * @since 4.11
280 *
281 * @return string Support text if super admin, empty string if not.
282 */
283 private function maybe_get_support_text() {
284 // If user isn't a super admin, don't return any text.
285 if ( ! is_super_admin() ) {
286 return '';
287 }
288
289 // If the user is an admin, return text with a link to support.
290 // We add a space here to seperate the sentences, but outside of the localized
291 // text to avoid it being removed.
292 return ' ' . sprintf(
293 // translators: %1$s start link, %2$s end link.
294 esc_html__( 'Please check out our %1$stroubleshooting guide%2$s for details on resolving this issue.', 'formidable' ),
295 '<a href="https://formidableforms.com/knowledgebase/add-spam-protection/">',
296 '</a>'
297 );
298 }
299
300 /**
301 * Clear third party cache plugins to avoid data-tokens missing or appearing when the antispam setting is changed.
302 *
303 * @return void
304 */
305 public static function clear_caches() {
306 self::clear_w3_total_cache();
307 self::clear_wp_fastest_cache();
308 self::clear_wp_super_cache();
309 self::clear_wp_optimize();
310 }
311
312 /**
313 * @return void
314 */
315 private static function clear_w3_total_cache() {
316 if ( is_callable( 'w3tc_flush_all' ) ) {
317 w3tc_flush_all();
318 }
319 }
320
321 /**
322 * @return void
323 */
324 private static function clear_wp_fastest_cache() {
325 do_action( 'wpfc_clear_all_cache' );
326 }
327
328 /**
329 * @return void
330 */
331 private static function clear_wp_super_cache() {
332 if ( function_exists( 'wp_cache_clean_cache' ) ) {
333 global $file_prefix;
334 wp_cache_clean_cache( $file_prefix, true );
335 }
336 }
337
338 /**
339 * @return void
340 */
341 private static function clear_wp_optimize() {
342 if ( class_exists( 'WP_Optimize' ) ) {
343 WP_Optimize()->get_page_cache()->purge();
344 }
345 }
346 }
347