PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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 trunk, at classes/models/FrmAntiSpam.php

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