PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / trunk
JetFormBuilder — Dynamic Blocks Form Builder vtrunk
3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.1.0 2.1.1 2.1.10 2.1.11 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 3.0.0 3.0.0.1 3.0.0.2 3.0.0.3 3.0.1 3.0.1.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.0.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.3.1 3.3.4 3.3.4.1 3.3.4.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.5.1 3.4.5.2 3.4.6 3.4.7 3.4.7.1 3.5.0 3.5.1 3.5.1.1 3.5.1.2 3.5.2 3.5.2.1 3.5.3 3.5.4 3.5.5 3.5.6 3.5.6.1 3.5.6.2 3.5.6.3 3.6.0
jetformbuilder / modules / security / honeypot / module.php
jetformbuilder / modules / security / honeypot Last commit date
module.php 3 weeks ago
module.php
261 lines
1 <?php
2
3
4 namespace JFB_Modules\Security\Honeypot;
5
6 use Jet_Form_Builder\Live_Form;
7 use JFB_Components\Module\Base_Module_It;
8 use JFB_Modules\Security\Exceptions\Spam_Exception;
9
10 // If this file is called directly, abort.
11 if ( ! defined( 'WPINC' ) ) {
12 die;
13 }
14
15 class Module implements Base_Module_It {
16
17
18 const FIELD_NAME = 'name';
19 const FIELD_EMAIL = 'email';
20 const FIELD_PHONE = 'phone';
21 const FIELD_COMPANY = 'company';
22
23 const SPAM_EXCEPTION = 'honeypot';
24
25 const FRONTEND_STYLE_HANDLE = 'jet-form-builder-frontend';
26
27 public function __construct() {
28 add_filter( 'jet-form-builder/security/spam-statuses', array( $this, 'add_spam_statuses' ) );
29 }
30
31 public function rep_item_id() {
32 return 'honeypot';
33 }
34
35 public function condition(): bool {
36 return true;
37 }
38
39 public function add_spam_statuses( $statuses ) {
40 $statuses[] = self::SPAM_EXCEPTION;
41
42 return $statuses;
43 }
44
45 public function init_hooks() {
46 add_filter(
47 'jet-form-builder/before-render-field',
48 array( $this, 'on_render_field' ),
49 10,
50 3
51 );
52
53 add_filter(
54 'jet-form-builder/request-handler/request',
55 array( $this, 'handle_request' )
56 );
57
58 add_filter(
59 'jet-form-builder/message-types',
60 array( $this, 'handle_global_messages' )
61 );
62
63 add_action(
64 'wp_enqueue_scripts',
65 array( $this, 'enqueue_styles' ),
66 20
67 );
68 }
69
70 public function remove_hooks() {
71 remove_filter(
72 'jet-form-builder/before-render-field',
73 array( $this, 'on_render_field' )
74 );
75
76 remove_filter(
77 'jet-form-builder/request-handler/request',
78 array( $this, 'handle_request' )
79 );
80
81 remove_filter(
82 'jet-form-builder/message-types',
83 array( $this, 'handle_global_messages' )
84 );
85
86 remove_action(
87 'wp_enqueue_scripts',
88 array( $this, 'enqueue_styles' ),
89 20
90 );
91 }
92
93 public function enqueue_styles() {
94 wp_add_inline_style(
95 self::FRONTEND_STYLE_HANDLE,
96 '
97 .jfb-user-info {
98 position: absolute;
99 top: 0;
100 left: 0;
101 width: 1px;
102 height: 1px;
103 overflow: hidden;
104 clip-path: inset(50%);
105 white-space: nowrap;
106 user-select: none;
107 -webkit-user-select: none;
108 }
109 '
110 );
111 }
112
113 private function get_honeypot_base_names(): array {
114 return array(
115 self::FIELD_NAME,
116 self::FIELD_EMAIL,
117 self::FIELD_PHONE,
118 self::FIELD_COMPANY,
119 );
120 }
121
122 private function get_honeypot_field_names( array $blocks ): array {
123 $existing_names = array_fill_keys(
124 $this->get_existing_field_names( $blocks ),
125 true
126 );
127
128 $honeypot_names = array();
129
130 foreach ( $this->get_honeypot_base_names() as $base_name ) {
131 $honeypot_names[] = $this->resolve_honeypot_field_name(
132 $base_name,
133 $existing_names
134 );
135
136 $existing_names[ end( $honeypot_names ) ] = true;
137 }
138
139 return $honeypot_names;
140 }
141
142 private function resolve_honeypot_field_name( string $base_name, array $existing_names ): string {
143 $candidates = array(
144 $base_name,
145 '_' . $base_name,
146 sprintf( '_jfb_%s_hp_', $base_name ),
147 );
148
149 foreach ( $candidates as $candidate ) {
150 if ( empty( $existing_names[ $candidate ] ) ) {
151 return $candidate;
152 }
153 }
154
155 $index = 2;
156
157 do {
158 $candidate = sprintf( '_jfb_%s_hp_%d', $base_name, $index );
159 ++$index;
160 } while ( ! empty( $existing_names[ $candidate ] ) );
161
162 return $candidate;
163 }
164
165 private function get_existing_field_names( array $blocks ): array {
166 $result = array();
167
168 $this->walk_blocks_for_field_names( $blocks, $result );
169
170 return array_values( array_unique( $result ) );
171 }
172
173 private function walk_blocks_for_field_names( array $blocks, array &$result ) {
174 foreach ( $blocks as $block ) {
175 if ( ! is_array( $block ) ) {
176 continue;
177 }
178
179 if ( isset( $block['attrs']['name'] ) && is_string( $block['attrs']['name'] ) ) {
180 $result[] = $block['attrs']['name'];
181 }
182
183 if ( isset( $block['name'] ) && is_string( $block['name'] ) ) {
184 $result[] = $block['name'];
185 }
186
187 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
188 $this->walk_blocks_for_field_names( $block['innerBlocks'], $result );
189 }
190 }
191 }
192
193 public function on_render_field( string $content, string $field_name, array $attrs ): string {
194 $type = $attrs['action_type'] ?? '';
195
196 if ( 'submit-field' !== $field_name || 'submit' !== $type ) {
197 return $content;
198 }
199
200 $args = jet_form_builder()->post_type->get_args();
201
202 if ( empty( $args['use_honeypot'] ) ) {
203 return $content;
204 }
205
206 $fields = '';
207
208 foreach ( $this->get_honeypot_field_names( Live_Form::instance()->blocks ) as $name ) {
209 $fields .= sprintf(
210 '<input type="text" name="%s" autocomplete="one-time-code">',
211 esc_attr( $name )
212 );
213 }
214
215 $content .= sprintf(
216 '<div class="jfb-user-info" inert>%s</div>',
217 $fields
218 );
219
220 return $content;
221 }
222
223 /**
224 * @param array $request
225 *
226 * @return array
227 * @throws Spam_Exception
228 */
229 public function handle_request( array $request ): array {
230 $args = jet_form_builder()->post_type->get_args();
231
232 if ( empty( $args['use_honeypot'] ) ) {
233 return $request;
234 }
235
236 $honeypot_names = $this->get_honeypot_field_names(
237 jet_form_builder()->form_handler->request_handler->_fields
238 );
239
240 foreach ( $honeypot_names as $honeypot_name ) {
241 if ( ! empty( $request[ $honeypot_name ] ) ) {
242 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
243 throw new Spam_Exception( self::SPAM_EXCEPTION );
244 }
245
246 unset( $request[ $honeypot_name ] );
247 }
248
249 return $request;
250 }
251
252 public function handle_global_messages( array $types ): array {
253 $types[ self::SPAM_EXCEPTION ] = array(
254 'label' => __( 'Honeypot validation failed', 'jet-form-builder' ),
255 'value' => __( 'You are not allowed to fill in the honeypot field', 'jet-form-builder' ),
256 );
257
258 return $types;
259 }
260 }
261