PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.24.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.24.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.24.1, at classes/models/FrmHoneypot.php

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