PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.32.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.32.1
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 / FrmHoneypot.php

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

341 lines 7.6 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 class FrmHoneypot extends FrmValidate {
7
8 /**
9 * Track the printed selectors so we do not print the same CSS twice.
10 *
11 * @since 6.22
12 *
13 * @var array
14 */
15 private static $printed_honeypot_selectors = array();
16
17 /**
18 * Option type.
19 *
20 * @since 6.21
21 *
22 * @var string
23 */
24 protected $option_type = 'global';
25
26 /**
27 * @return string
28 */
29 protected function get_option_key() {
30 return 'honeypot';
31 }
32
33 /**
34 * @return bool
35 */
36 private static function is_enabled() {
37 return (bool) FrmAppHelper::get_settings()->honeypot;
38 }
39
40 /**
41 * @return bool
42 */
43 public function validate() {
44 if ( ! $this->is_option_on() || ! $this->check_honeypot_filter() ) {
45 // Never flag as honeypot spam if disabled.
46 return true;
47 }
48 return ! $this->is_honeypot_spam();
49 }
50
51 /**
52 * @return bool
53 */
54 private function is_honeypot_spam() {
55 $is_honeypot_spam = $this->is_legacy_honeypot_spam();
56
57 if ( ! $is_honeypot_spam ) {
58 $field_id = $this->get_honeypot_field_id();
59
60 if ( ! $field_id ) {
61 return false;
62 }
63
64 $value = $this->get_honeypot_field_value( $field_id );
65 $is_honeypot_spam = '' !== $value;
66 }
67
68 $atts = array(
69 'form' => $this->get_form(),
70 );
71
72 /**
73 * Filters the honeypot spam check.
74 *
75 * @param bool $is_honeypot_spam Set to `true` if is spam.
76 * @param array $atts Contains `form` and `fields`.
77 */
78 return apply_filters( 'frm_process_honeypot', $is_honeypot_spam, $atts );
79 }
80
81 /**
82 * Check the old frm_verify key. We'll continue to consider any entry with an frm_verify value as spam.
83 *
84 * @return bool
85 */
86 private function is_legacy_honeypot_spam() {
87 $legacy_honeypot_value = FrmAppHelper::get_param( 'frm_verify', '', 'get', 'sanitize_text_field' );
88 return '' !== $legacy_honeypot_value;
89 }
90
91 /**
92 * @return mixed either true, or false.
93 */
94 private function check_honeypot_filter() {
95 $form = $this->get_form();
96 return apply_filters( 'frm_run_honeypot', true, compact( 'form' ) );
97 }
98
99 /**
100 * @param int $form_id Form ID.
101 *
102 * @return void
103 */
104 public static function maybe_render_field( $form_id ) {
105 $honeypot = new self( $form_id );
106
107 if ( ! $honeypot->should_render_field() ) {
108 return;
109 }
110
111 $max_field_id = FrmDb::get_var(
112 'frm_fields',
113 array(),
114 'id',
115 array(
116 'order_by' => 'id DESC',
117 )
118 );
119
120 global $frm_vars;
121 $offset = isset( $frm_vars['honeypot_selectors'] ) ? count( $frm_vars['honeypot_selectors'] ) + 1 : 1;
122 $honeypot_field_id = $max_field_id ? $max_field_id + $offset : $offset;
123 $class = class_exists( 'FrmProFormState' ) ? 'FrmProFormState' : 'FrmFormState';
124 $class::set_initial_value( 'honeypot_field_id', $honeypot_field_id );
125
126 $honeypot->render_field( $honeypot_field_id );
127 self::maybe_print_honeypot_css();
128 }
129
130 /**
131 * Maybe print honeypot JS.
132 *
133 * @since 6.21
134 *
135 * @return void
136 */
137 public static function maybe_print_honeypot_js() {
138 if ( FrmAppHelper::is_admin() || ! self::is_enabled() ) {
139 return;
140 }
141
142 $css = self::get_honeypot_field_css();
143
144 if ( ! $css ) {
145 return;
146 }
147
148 // There must be no empty lines inside the script. Otherwise, wpautop adds <p> tags which break script execution.
149 printf(
150 "<script>
151 ( function() {
152 const style = document.createElement( 'style' );
153 style.appendChild( document.createTextNode( '%s' ) );
154 document.head.appendChild( style );
155 document.currentScript?.remove();
156 } )();
157 </script>",
158 esc_js( $css )
159 );
160
161 global $frm_vars;
162 self::$printed_honeypot_selectors = $frm_vars['honeypot_selectors'];
163 }
164
165 /**
166 * Maybe print honeypot CSS in case JS doesn't run.
167 *
168 * @since 6.21
169 *
170 * @return void
171 */
172 public static function maybe_print_honeypot_css() {
173 // Print the CSS if form is loaded by API.
174 if ( ! FrmFormsHelper::form_is_loaded_by_api() ) {
175 return;
176 }
177
178 $css = self::get_honeypot_field_css();
179
180 if ( $css ) {
181 echo '<style>' . esc_html( $css ) . '</style>';
182 }
183 }
184
185 /**
186 * Gets honeypot field CSS.
187 *
188 * @return string
189 */
190 private static function get_honeypot_field_css() {
191 global $frm_vars;
192
193 if ( empty( $frm_vars['honeypot_selectors'] ) ) {
194 return '';
195 }
196
197 $selectors = $frm_vars['honeypot_selectors'];
198
199 if ( self::$printed_honeypot_selectors ) {
200 $selectors = array_diff( $selectors, self::$printed_honeypot_selectors );
201
202 if ( ! $selectors ) {
203 return '';
204 }
205 }
206
207 return sprintf(
208 '%s {visibility:hidden;overflow:hidden;width:0;height:0;position:absolute;}',
209 implode( ',', $selectors )
210 );
211 }
212
213 /**
214 * @return bool
215 */
216 public function should_render_field() {
217 return $this->is_option_on() && $this->check_honeypot_filter();
218 }
219
220 /**
221 * @param int $honeypot_field_id
222 *
223 * @return void
224 */
225 public function render_field( $honeypot_field_id = 0 ) {
226 if ( ! $honeypot_field_id ) {
227 return;
228 }
229
230 $field_id = $honeypot_field_id;
231 $field_key = $this->get_honeypot_field_key();
232 $input_attrs = array(
233 'id' => 'field_' . $field_key,
234 'type' => 'text',
235 'class' => 'frm_form_field form-field frm_verify',
236 'name' => 'item_meta[' . $field_id . ']',
237 'value' => $this->get_honeypot_field_value( $field_id ),
238 );
239
240 $container_id = 'frm_field_' . $field_id . '_container';
241 $this->track_html_id( $container_id );
242 // phpcs:disable Generic.WhiteSpace.ScopeIndent
243 ?>
244 <div id="<?php echo esc_attr( $container_id ); ?>">
245 <label for="<?php echo esc_attr( $input_attrs['id'] ); ?>" <?php FrmFormsHelper::maybe_hide_inline(); ?>>
246 <?php esc_html_e( 'If you are human, leave this field blank.', 'formidable' ); ?>
247 </label>
248 <input <?php FrmAppHelper::array_to_html_params( $input_attrs, true ); ?> <?php FrmFormsHelper::maybe_hide_inline(); ?> />
249 </div>
250 <?php
251 // phpcs:enable Generic.WhiteSpace.ScopeIndent
252 }
253
254 /**
255 * @param string $html_id
256 *
257 * @return void
258 */
259 private function track_html_id( $html_id ) {
260 global $frm_vars;
261
262 if ( ! isset( $frm_vars['honeypot_selectors'] ) ) {
263 $frm_vars['honeypot_selectors'] = array();
264 }
265
266 $frm_vars['honeypot_selectors'][] = '#' . $html_id;
267 }
268
269 /**
270 * @return int
271 */
272 private function get_honeypot_field_id() {
273 $class = class_exists( 'FrmProFormState' ) ? 'FrmProFormState' : 'FrmFormState';
274 return $class::get_from_request( 'honeypot_field_id', 0 );
275 }
276
277 /**
278 * @return string
279 */
280 private function get_honeypot_field_key() {
281 return FrmAppHelper::generate_new_key( 5 );
282 }
283
284 /**
285 * Gets honeypot field value.
286 *
287 * @param string $field_id Field ID.
288 *
289 * @return string
290 */
291 private function get_honeypot_field_value( $field_id ) {
292 $item_meta = FrmAppHelper::get_simple_request(
293 array(
294 'param' => 'item_meta',
295 'default' => array(),
296 'type' => 'post',
297 )
298 );
299
300 if ( ! $item_meta || ! is_array( $item_meta ) ) {
301 return '';
302 }
303
304 return $item_meta[ $field_id ] ?? '';
305 }
306
307 /**
308 * Generate a random class name for our honeypot so it is less easy to detect.
309 *
310 * @return string The generated class name.
311 */
312 public static function generate_class_name() {
313 $class_name = self::get_honeypot_class_name();
314
315 if ( 'frm_verify' !== $class_name ) {
316 // Re-use the option.
317 // We can't generate a new class too often or the field may not be hidden.
318 return $class_name;
319 }
320
321 $prefix = 'frm__';
322 $class_name = $prefix . uniqid();
323 update_option( 'frm_honeypot_class', $class_name );
324 return $class_name;
325 }
326
327 /**
328 * @return string The current class name to use the for Honeypot field.
329 */
330 private static function get_honeypot_class_name() {
331 $option = get_option( 'frm_honeypot_class' );
332
333 if ( ! is_string( $option ) ) {
334 // For backward compatibility use the old class name.
335 return 'frm_verify';
336 }
337
338 return $option;
339 }
340 }
341