PluginProbe
FormLayer / trunk
FormLayer vtrunk
1.0.9 1.0.8 1.0.7 1.0.6 trunk 1.0.3 1.0.4 1.0.5
formlayer / main / frontend.php

frontend.php in FormLayer trunk, at main/frontend.php

847 lines 30.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FormLayer;
4
5 if(!defined('ABSPATH')){
6 exit;
7 }
8
9 class Frontend{
10
11 static function init(){
12 add_shortcode('formlayer', '\FormLayer\Frontend::render_shortcode');
13 add_action('wp_enqueue_scripts', '\FormLayer\Frontend::enqueue_assets');
14 }
15
16 static function enqueue_assets(){
17 wp_enqueue_style('dashicons');
18 wp_enqueue_style('formlayer-frontend', FORMLAYER_PLUGIN_URL . 'assets/css/frontend.css', [], FORMLAYER_VERSION);
19 wp_enqueue_script('formlayer-frontend', FORMLAYER_PLUGIN_URL . 'assets/js/frontend.js', ['jquery'], FORMLAYER_VERSION, true);
20
21 wp_localize_script('formlayer-frontend', 'formlayer_data', [
22 'ajax_url' => admin_url('admin-ajax.php'),
23 'nonce' => wp_create_nonce('formlayer-frontend'),
24 'is_pro' => defined('FORMLAYER_PRO_VERSION') ? true : false,
25 ]);
26 }
27
28 static function render_shortcode($atts){
29 $atts = shortcode_atts([
30 'id' => 0
31 ], $atts);
32
33 if(empty($atts['id'])){
34 return '<div class="formlayer-error">' . esc_html__('Please specify form ID: [formlayer id="123"]', 'formlayer') . '</div>';
35 }
36
37 $id = intval($atts['id']);
38 $form = get_post($id);
39
40 if(!$form || 'formlayer_form' !== $form->post_type){
41 // Try finding by display ID
42 $forms = get_posts([
43 'post_type' => 'formlayer_form',
44 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
45 'meta_query' => [
46 [
47 'key' => '_formlayer_display_id',
48 'value' => $id,
49 'compare' => '='
50 ]
51 ],
52 'posts_per_page' => 1
53 ]);
54
55 if(!empty($forms)){
56 $form = $forms[0];
57 }
58 }
59
60 if(empty($form)){
61 /* translators: %s: Form ID */
62 return '<div class="formlayer-error">' . esc_html(sprintf(__('Form not found (ID: %s)', 'formlayer'), $atts['id'])) . '</div>';
63 }
64
65 if('formlayer_form' !== $form->post_type){
66 return '<div class="formlayer-error">' . esc_html__('Invalid post type. Expected formlayer_form.', 'formlayer') . '</div>';
67 }
68
69 if('publish' !== $form->post_status && 'draft' !== $form->post_status){
70 return '<div class="formlayer-error">' . esc_html__('Form is not published', 'formlayer') . '</div>';
71 }
72
73 self::enqueue_captcha_scripts($form);
74
75 return self::render_form($form);
76 }
77
78 static function render_form($form){
79 $form_id = $form->ID;
80 $content = $form->post_content;
81
82 $is_json = false;
83 $data = json_decode($content, true);
84 if (json_last_error() === JSON_ERROR_NONE && is_array($data) && isset($data['fields'])) {
85 $is_json = true;
86 }
87
88 // Inject Custom CSS
89 if($is_json && ! empty( $data['settings']['custom_css'])){
90 $handle = 'formlayer-custom-' . intval( $form_id );
91
92 wp_register_style( $handle, false, [], FORMLAYER_VERSION );
93 wp_enqueue_style( $handle );
94
95 $custom_css = $data['settings']['custom_css'];
96
97 // Allow only safe CSS (basic hardening)
98 $custom_css = wp_kses( $custom_css, [] ); // removes HTML tags
99
100 wp_add_inline_style( $handle, $custom_css );
101 }
102
103 $has_submit = false;
104 if ($is_json) {
105 $html = self::render_json_form($data);
106 foreach ($data['fields'] as $f){
107 if($f['type'] === 'submit'){
108 $has_submit = true;
109 break;
110 }
111 }
112 } else {
113 //apply kses for HTML sanitization
114 $html = do_blocks($content);
115
116 // Sanitize HTML from do_blocks() - allow only safe HTML/CSS
117 $allowed_html = wp_kses_allowed_html('post');
118 // Add extra allowed attributes if needed for forms
119 $allowed_html['div']['data-*'] = true;
120 $allowed_html['form']['data-*'] = true;
121 $allowed_html['input']['data-*'] = true;
122 $allowed_html['button']['data-*'] = true;
123
124 $html = wp_kses($html, $allowed_html);
125
126 if(empty(trim(wp_strip_all_tags($html)))){
127 $html = self::render_form_fallback($form);
128 $html = wp_kses($html, $allowed_html);
129 }
130 }
131
132 $output = '<div class="formlayer-form-wrapper" id="formlayer-form-' . esc_attr($form_id) . '">
133 <form class="formlayer-form" data-form-id="' . esc_attr($form_id) . '" enctype="multipart/form-data">
134 <div class="formlayer-form-status"></div>
135 <div class="formlayer-form-fields-wrapper">
136 ' .$html; // sanitized above
137
138 if(!$has_submit){
139 $output .= '<div class="formlayer-submit-wrap">
140 <button type="submit" class="formlayer-submit-btn">' . esc_html__('Submit Form', 'formlayer') . '</button>
141 </div>';
142 }
143
144 $output .= '</div>
145 </form>
146 </div>';
147
148 return $output;
149 }
150
151 static function render_json_form($data) {
152 $html = '';
153 $fields = isset($data['fields']) ? $data['fields'] : [];
154
155 foreach ($fields as $field) {
156 $html .= self::render_field_html($field);
157 }
158
159 return $html;
160 }
161
162 static function render_field_html($field) {
163 $type = isset($field['type']) ? $field['type'] : 'text';
164 $label = isset($field['label']) ? $field['label'] : '';
165 $placeholder = isset($field['placeholder']) ? $field['placeholder'] : '';
166 $required = !empty($field['required']) ? 'required' : '';
167
168 // Advanced Props
169 $label_placement = isset($field['label_placement']) ? $field['label_placement'] : 'top';
170 $container_class = isset($field['container_class']) ? $field['container_class'] : '';
171 $element_class = isset($field['element_class']) ? $field['element_class'] : '';
172 $help_text = isset($field['help_text']) ? $field['help_text'] : '';
173 $default_value = isset($field['default_value']) ? $field['default_value'] : (isset($field['value']) ? $field['value'] : '');
174 $label_color = isset($field['style_label_color']) ? $field['style_label_color'] : '';
175 $border_radius = isset($field['style_border_radius']) ? $field['style_border_radius'] : '';
176
177 $name = \FormLayer\Util::get_field_name($field);
178 $req_mark = $required ? '<span class="required-indicator">*</span>' : '';
179
180 // Styles
181 $label_style = !empty($label_color) ? 'style="color:' . esc_attr($label_color) . ';"' : '';
182 $input_style = !empty($border_radius) ? 'style="border-radius:' . intval($border_radius) . 'px;"' : '';
183
184 // Allow Pro to override rendering
185 $pro_html = apply_filters('formlayer_render_field_html', '', $field);
186 if (!empty($pro_html)) {
187 return self::sanitize_field_html($pro_html);
188 }
189
190 $html = '<div class="formlayer-field-wrap label-' . esc_attr($label_placement) . ' ' . esc_attr($container_class) . '">';
191
192 if($label && !in_array($type, ['submit', 'section', 'hidden', 'terms', 'gdpr']) && 'label-hidden' !== $label_placement && 'hidden' !== $label_placement){
193 $html .= '<label class="formlayer-label" ' . $label_style . '>' . esc_html($label) . $req_mark . '</label>';
194 }
195
196 $input_class = 'formlayer-input ' . esc_attr($element_class);
197
198 switch ($type) {
199 case 'textarea':
200 $rows = isset($field['rows']) ? intval($field['rows']) : 4;
201 $cols = isset($field['cols']) ? intval($field['cols']) : '';
202 $style = !empty($cols) ? 'width:' . intval($cols) . 'px !important;' : 'width:100%;';
203 if (!empty($border_radius)) $style .= 'border-radius:' . intval($border_radius) . 'px;';
204 $html .= '<textarea name="' . esc_attr($name) . '" class="' . $input_class . '" placeholder="' . esc_attr($placeholder) . '" ' . $required . ' style="' . esc_attr($style) . '" rows="' . $rows . '" cols="' . esc_attr($cols) . '">' . esc_textarea($default_value) . '</textarea>';
205 break;
206 case 'dropdown':
207 $options = isset($field['options']) && !empty($field['options']) ? $field['options'] : ['Option 1', 'Option 2', 'Option 3'];
208 if (is_string($options)) $options = explode("\n", $options);
209
210 $html .= '<select name="' . esc_attr($name) . '" class="' . $input_class . '" ' . $required . ' ' . $input_style . ' style="width:100%;">';
211 $html .= '<option value="">' . esc_html($placeholder ? $placeholder : __('Select Option', 'formlayer')) . '</option>';
212 foreach($options as $opt) {
213 $o = is_array($opt) ? $opt : ['label' => $opt, 'value' => $opt, 'default' => false];
214 $selected = ($default_value === $o['value'] || (empty($default_value) && !empty($o['default']))) ? 'selected' : '';
215 $html .= '<option value="' . esc_attr($o['value']) . '" ' . $selected . '>' . esc_html($o['label']) . '</option>';
216 }
217 $html .= '</select>';
218 break;
219 case 'radio':
220 case 'checkbox':
221 case 'multiple':
222 $options = isset($field['options']) && !empty($field['options']) ? $field['options'] : ['Option 1', 'Option 2', 'Option 3'];
223 if (is_string($options)) $options = explode("\n", $options);
224
225 $html .= '<div class="formlayer-options-list">';
226 foreach($options as $opt) {
227 $o = is_array($opt) ? $opt : ['label' => $opt, 'value' => $opt, 'default' => false];
228 $input_type = ($type === 'radio') ? 'radio' : 'checkbox';
229 $field_name = ($type === 'radio') ? $name : $name . '[]';
230
231 $checked = '';
232 if ($type === 'radio') {
233 $checked = ($default_value === $o['value'] || (empty($default_value) && !empty($o['default']))) ? 'checked' : '';
234 } else {
235 // Checkbox/Multiple
236 if (is_array($default_value)) {
237 $checked = in_array($o['value'], $default_value) ? 'checked' : '';
238 } elseif (!empty($default_value)) {
239 $checked = ($default_value === $o['value']) ? 'checked' : '';
240 } else {
241 $checked = !empty($o['default']) ? 'checked' : '';
242 }
243 }
244
245 $html .= '
246 <label class="formlayer-option-label">
247 <input type="' . $input_type . '" name="' . esc_attr($field_name) . '" value="' . esc_attr($o['value']) . '" ' . $checked . '>
248 <span>' . esc_html($o['label']) . '</span>
249 </label>';
250 }
251 $html .= '</div>';
252 break;
253 case 'email':
254 $html .= '<input type="email" name="' . esc_attr($name) . '" class="' . $input_class . '" placeholder="' . esc_attr($placeholder) . '" value="' . esc_attr($default_value) . '" ' . $required . ' ' . $input_style . '>';
255 break;
256 case 'name':
257 $show_first = !isset($field['enable_first_name']) || $field['enable_first_name'] !== false;
258 $show_middle = !empty($field['enable_middle_name']);
259 $show_last = !isset($field['enable_last_name']) || $field['enable_last_name'] !== false;
260
261 $p1 = isset($field['placeholder_first']) && $field['placeholder_first'] !== '' ? $field['placeholder_first'] : 'First Name';
262 $p2 = isset($field['placeholder_middle']) && $field['placeholder_middle'] !== '' ? $field['placeholder_middle'] : 'Middle Name';
263 $p3 = isset($field['placeholder_last']) && $field['placeholder_last'] !== '' ? $field['placeholder_last'] : 'Last Name';
264
265 $l1 = isset($field['label_first']) && $field['label_first'] !== '' ? $field['label_first'] : __('First Name', 'formlayer');
266 $l2 = isset($field['label_middle']) && $field['label_middle'] !== '' ? $field['label_middle'] : __('Middle Name', 'formlayer');
267 $l3 = isset($field['label_last']) && $field['label_last'] !== '' ? $field['label_last'] : __('Last Name', 'formlayer');
268
269 $html .= '<div class="formlayer-name-fields-wrap">';
270 if($show_first) {
271 $html .= '<div class="formlayer-sub-field">
272 <input type="text" name="' . esc_attr($name) . '[first]" class="' . $input_class . '" placeholder="' . esc_attr($p1) . '" ' . $required . '>
273 <span class="formlayer-sub-label">' . esc_html($l1) . '</span>
274 </div>';
275 }
276 if($show_middle) {
277 $html .= '<div class="formlayer-sub-field">
278 <input type="text" name="' . esc_attr($name) . '[middle]" class="' . $input_class . '" placeholder="' . esc_attr($p2) . '">
279 <span class="formlayer-sub-label">' . esc_html($l2) . '</span>
280 </div>';
281 }
282 if($show_last) {
283 $html .= '<div class="formlayer-sub-field">
284 <input type="text" name="' . esc_attr($name) . '[last]" class="' . $input_class . '" placeholder="' . esc_attr($p3) . '" ' . $required . '>
285 <span class="formlayer-sub-label">' . esc_html($l3) . '</span>
286 </div>';
287 }
288 $html .= '</div>';
289 break;
290 case 'country':
291 $html .= '<div class="formlayer-select-wrap">
292 <span class="dashicons dashicons-admin-site"></span>
293 <select name="' . esc_attr($name) . '" class="' . $input_class . '" ' . $required . '>';
294 $html .= '<option value="">' . esc_html($placeholder ? $placeholder : 'Select Country') . '</option>';
295 foreach(self::get_countries() as $code => $label) {
296 $selected = ($default_value === $code || $default_value === $label) ? 'selected' : '';
297 $html .= '<option value="' . esc_attr($code) . '" ' . $selected . '>' . esc_html($label) . '</option>';
298 }
299 $html .= '</select>
300 </div>';
301 break;
302 case 'mask':
303 $html .= '<input type="text" name="' . esc_attr($name) . '" class="' . $input_class . ' formlayer-masked-input" placeholder="' . esc_attr($placeholder ? $placeholder : '(+1) 000-0000') . '" value="' . esc_attr($default_value) . '" ' . $required . '>';
304 break;
305 case 'rating':
306 $html .= '<div class="formlayer-rating" data-name="' . esc_attr($name) . '">';
307 for($i=1; $i<=5; $i++){
308 $html .= '<span class="dashicons dashicons-star-filled" data-value="' . $i . '"></span>';
309 }
310 $html .= '<input type="hidden" name="' . esc_attr($name) . '" value="' . esc_attr($default_value) . '">';
311 $html .= '</div>';
312 break;
313 case 'section':
314 return '<div class="formlayer-section-break"><h3>' . esc_html($label) . '</h3>' . ($help_text ? '<p>' . esc_html($help_text) . '</p>' : '') . '</div>';
315 case 'number':
316 $min = isset($field['min']) && $field['min'] !== '' ? 'min="' . esc_attr($field['min']) . '"' : '';
317 $max = isset($field['max']) && $field['max'] !== '' ? 'max="' . esc_attr($field['max']) . '"' : '';
318 $digit_limit = isset($field['digit_limit']) && $field['digit_limit'] !== '' ? 'maxlength="' . esc_attr($field['digit_limit']) . '"' : '';
319 $html .= '<input type="number" name="' . esc_attr($name) . '" class="' . $input_class . '" placeholder="' . esc_attr($placeholder) . '" value="' . esc_attr($default_value) . '" ' . $required . ' ' . $input_style . ' ' . $min . ' ' . $max . ' ' . $digit_limit . '>';
320 break;
321 case 'terms':
322 // Use terms_label if set, else fall back to the admin label field
323 $raw_terms_label = isset($field['terms_label']) && $field['terms_label'] !== '' ? $field['terms_label'] : ($label ? $label : __('I agree to the Terms &amp; Conditions', 'formlayer'));
324 $html .= '
325 <div class="formlayer-terms-wrap">
326 <input type="checkbox" name="' . esc_attr($name) . '" ' . $required . '>
327 <span class="formlayer-terms-label" ' . $label_style . '>' . wp_kses_post($raw_terms_label) . '</span>
328 </div>';
329 break;
330 case 'gdpr':
331 $gdpr_label = isset($field['gdpr_label']) ? $field['gdpr_label'] : 'Accept GDPR Policy';
332 $gdpr_desc = isset($field['gdpr_description']) ? $field['gdpr_description'] : '';
333 $html .= '
334 <div class="formlayer-gdpr-wrap">
335 <input type="checkbox" name="' . esc_attr($name) . '" ' . $required . '>
336 <div class="formlayer-gdpr-info">
337 <div class="formlayer-gdpr-label" ' . $label_style . '>' . wp_kses_post($gdpr_label) . '</div>
338 ' . ($gdpr_desc ? '<div class="formlayer-gdpr-desc">' . wp_kses_post($gdpr_desc) . '</div>' : '') . '
339 </div>
340 </div>';
341 break;
342 case 'captcha':
343 $settings = get_option('formlayer_settings', []);
344 $provider = isset($field['captcha_provider']) ? $field['captcha_provider'] : 'hcaptcha';
345
346 // Get global default theme based on provider
347 $global_theme = 'light';
348 if($provider === 'hcaptcha') $global_theme = isset($settings['captcha_h_theme']) ? $settings['captcha_h_theme'] : 'light';
349 // Pro captcha providers - delegate to Pro plugin via filter
350 $global_theme = apply_filters('formlayer_pro_captcha_theme', $global_theme, $provider);
351
352 $theme = isset($field['captcha_theme']) ? $field['captcha_theme'] : $global_theme;
353 $site_key = '';
354
355 if($provider === 'hcaptcha') $site_key = isset($settings['captcha_h_site_key']) ? $settings['captcha_h_site_key'] : '';
356 // Pro captcha providers - delegate to Pro plugin via filter
357 $site_key = apply_filters('formlayer_pro_captcha_sitekey', $site_key, $provider);
358
359 if(empty($site_key)) return '';
360
361 $html .= '<div class="formlayer-field-item formlayer-captcha-container" data-provider="'.esc_attr($provider).'" data-sitekey="'.esc_attr($site_key).'" data-theme="'.esc_attr($theme).'">';
362 if($provider === 'hcaptcha') {
363 $html .= '<div class="h-captcha" data-sitekey="'.esc_attr($site_key).'" data-theme="'.esc_attr($theme).'"></div>';
364 } else {
365 // Pro captcha providers - delegate to Pro plugin via filter
366 $pro_html = apply_filters('formlayer_pro_captcha_html', '', $provider, $field);
367 $html .= wp_kses_post($pro_html);
368 }
369 $html .= '</div>';
370 break;
371 case 'submit':
372 $align = isset($field['btn_align']) ? $field['btn_align'] : 'left';
373 $size = isset($field['btn_size']) ? $field['btn_size'] : 'md';
374 $bg = isset($field['btn_bg_color']) ? $field['btn_bg_color'] : '';
375 $txt = isset($field['btn_text_color']) ? $field['btn_text_color'] : '';
376 $bg_h = isset($field['btn_bg_hover']) ? $field['btn_bg_hover'] : '';
377 $txt_h = isset($field['btn_text_hover']) ? $field['btn_text_hover'] : '';
378 $rad = isset($field['style_border_radius']) ? intval($field['style_border_radius']) . 'px' : '';
379
380 $btn_id = 'fl-btn-' . (isset($field['id']) ? $field['id'] : uniqid());
381 $btn_style = '';
382 if($bg) $btn_style .= 'background-color:' . esc_attr($bg) . ' !important;';
383 if($txt) $btn_style .= 'color:' . esc_attr($txt) . ' !important;';
384 if($rad) $btn_style .= 'border-radius:' . esc_attr($rad) . ' !important;';
385
386 $size_class = 'btn-' . esc_attr($size);
387 $align_class = 'align-' . esc_attr($align);
388
389 if ($bg_h || $txt_h) {
390 $hover_css = "#" . esc_attr($btn_id) . ":hover {";
391 if($bg_h) $hover_css .= "background-color: " . esc_attr($bg_h) . " !important;";
392 if($txt_h) $hover_css .= "color: " . esc_attr($txt_h) . " !important;";
393 $hover_css .= "}";
394 wp_add_inline_style('formlayer-frontend', wp_strip_all_tags($hover_css));
395 }
396
397 return '<div class="formlayer-submit-wrap ' . esc_attr($align_class) . '">
398 <button type="submit" id="' . esc_attr($btn_id) . '" class="formlayer-submit-btn ' . esc_attr($size_class) . ' ' . esc_attr($element_class) . '" style="' . $btn_style . '">' . esc_html($label ? $label : 'Submit Form') . '</button>
399 </div>';
400 default:
401 $extra_attrs = '';
402 if ($type === 'url') {
403 if (!empty($field['url_validation'])) $extra_attrs .= ' data-validate-url="1"';
404 if (!empty($field['url_https_only'])) $extra_attrs .= ' data-https-only="1"';
405 }
406 if ($type === 'phone' && empty($placeholder)) {
407 $placeholder = __('Phone Number', 'formlayer');
408 }
409 $input_type = in_array($type, ['password', 'url', 'tel', 'phone', 'hidden']) ? ($type === 'phone' ? 'tel' : $type) : 'text';
410 $html .= '<input type="' . esc_attr($input_type) . '" name="' . esc_attr($name) . '" class="' . $input_class . '" placeholder="' . esc_attr($placeholder) . '" value="' . esc_attr($default_value) . '" ' . $required . ' ' . $input_style . $extra_attrs . '>';
411 break;
412 }
413
414 if (!empty($help_text)) {
415 $html .= '<div class="formlayer-help-text">' . esc_html($help_text) . '</div>';
416 }
417
418 $html .= '</div>';
419 return $html;
420 }
421
422 // Sanitizes field HTML to allow standard form tags while preventing XSS
423 static function sanitize_field_html($html){
424 $allowed = wp_kses_allowed_html('post');
425
426 $allowed['input'] = [
427 'type' => true,
428 'name' => true,
429 'value' => true,
430 'class' => true,
431 'placeholder' => true,
432 'required' => true,
433 'checked' => true,
434 'style' => true,
435 'id' => true,
436 'data-*' => true,
437 'disabled' => true,
438 'readonly' => true,
439 'min' => true,
440 'max' => true,
441 'maxlength' => true,
442 ];
443 $allowed['select'] = [
444 'name' => true,
445 'class' => true,
446 'required' => true,
447 'style' => true,
448 'id' => true,
449 'data-*' => true,
450 'disabled' => true,
451 ];
452 $allowed['option'] = [
453 'value' => true,
454 'selected' => true,
455 'disabled' => true,
456 ];
457 $allowed['textarea'] = [
458 'name' => true,
459 'class' => true,
460 'placeholder' => true,
461 'required' => true,
462 'style' => true,
463 'id' => true,
464 'rows' => true,
465 'cols' => true,
466 'data-*' => true,
467 'disabled' => true,
468 'readonly' => true,
469 ];
470 $allowed['label'] = [
471 'class' => true,
472 'style' => true,
473 'for' => true,
474 ];
475 $allowed['button'] = [
476 'type' => true,
477 'class' => true,
478 'style' => true,
479 'id' => true,
480 'data-*' => true,
481 'disabled' => true,
482 ];
483 $allowed['div']['data-*'] = true;
484 $allowed['div']['class'] = true;
485 $allowed['div']['style'] = true;
486 $allowed['span']['data-*'] = true;
487 $allowed['span']['class'] = true;
488 $allowed['span']['style'] = true;
489 $allowed['p']['class'] = true;
490 $allowed['p']['style'] = true;
491
492 return wp_kses($html, $allowed);
493 }
494
495 static function render_form_fallback($form){
496 $form_id = $form->ID;
497 $blocks = parse_blocks($form->post_content);
498
499 $html = '';
500 foreach($blocks as $block){
501 $html .= self::render_block_fallback($block);
502 }
503 return $html;
504 }
505
506 static function render_block_fallback($block){
507 $attrs = isset($block['attrs']) ? $block['attrs'] : [];
508 $inner_html = isset($block['innerHTML']) ? $block['innerHTML'] : '';
509
510 switch($block['blockName']){
511 case 'formlayer/text-field':
512 $label = isset($attrs['label']) ? $attrs['label'] : 'Text Field';
513 $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : '';
514 $required = !empty($attrs['required']) ? ' required' : '';
515 return '<div class="formlayer-field-wrap">
516 <label class="formlayer-label">' . esc_html($label) . ($required ? '<span class="required-indicator">*</span>' : '') . '</label>
517 <input type="text" name="field_text" class="formlayer-input" placeholder="' . esc_attr($placeholder) . '"' . $required . '>
518 </div>';
519
520 case 'formlayer/email-field':
521 $label = isset($attrs['label']) ? $attrs['label'] : 'Email';
522 $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : '';
523 $required = !empty($attrs['required']) ? ' required' : '';
524 return '<div class="formlayer-field-wrap">
525 <label class="formlayer-label">' . esc_html($label) . ($required ? '<span class="required-indicator">*</span>' : '') . '</label>
526 <input type="email" name="field_email" class="formlayer-input" placeholder="' . esc_attr($placeholder) . '"' . $required . '>
527 </div>';
528
529 case 'formlayer/textarea-field':
530 $label = isset($attrs['label']) ? $attrs['label'] : 'Message';
531 $placeholder = isset($attrs['placeholder']) ? $attrs['placeholder'] : '';
532 $required = !empty($attrs['required']) ? ' required' : '';
533 return '<div class="formlayer-field-wrap">
534 <label class="formlayer-label">' . esc_html($label) . ($required ? '<span class="required-indicator">*</span>' : '') . '</label>
535 <textarea name="field_textarea" class="formlayer-input" placeholder="' . esc_attr($placeholder) . '"' . $required . '></textarea>
536 </div>';
537
538 case 'formlayer/submit-button':
539 $text = isset($attrs['text']) ? $attrs['text'] : 'Submit';
540 return '<div class="formlayer-submit-wrap">
541 <button type="submit" class="formlayer-submit-btn">' . esc_html($text) . '</button>
542 </div>';
543
544 default:
545 return $inner_html;
546 }
547 }
548
549 static function enqueue_captcha_scripts($form = null){
550 if(!$form){
551 return;
552 }
553
554 $providers = [];
555 $content = $form->post_content;
556 $data = json_decode($content, true);
557
558 if (json_last_error() === JSON_ERROR_NONE && is_array($data) && isset($data['fields'])) {
559 foreach($data['fields'] as $field){
560 if(isset($field['type']) && $field['type'] === 'captcha'){
561 $providers[] = isset($field['captcha_provider']) ? $field['captcha_provider'] : 'hcaptcha';
562 }
563 }
564 } else {
565 if(strpos($content, 'hcaptcha') !== false){
566 $providers[] = 'hcaptcha';
567 }
568
569 if(strpos($content, 'recaptcha') !== false){
570 $providers[] = 'recaptcha';
571 }
572
573 if(strpos($content, 'turnstile') !== false){
574 $providers[] = 'turnstile';
575 }
576
577 $blocks = parse_blocks($content);
578 foreach($blocks as $block){
579 if(strpos($block['blockName'], 'captcha') !== false || strpos($block['innerHTML'], 'captcha') !== false){
580 $attrs = isset($block['attrs']) ? $block['attrs'] : [];
581 if(isset($attrs['captcha_provider'])){
582 $providers[] = $attrs['captcha_provider'];
583 } elseif(isset($attrs['provider'])){
584 $providers[] = $attrs['provider'];
585 }
586 }
587 }
588 }
589
590 $providers = array_unique($providers);
591 if(empty($providers)){
592 return;
593 }
594
595 $settings = get_option('formlayer_settings', []);
596 if (in_array('hcaptcha', $providers, true) && !empty($settings['captcha_h_site_key'])) {
597 wp_enqueue_script('hcaptcha', 'https://js.hcaptcha.com/1/api.js', [], FORMLAYER_VERSION, true);
598 }
599
600 // Pro captcha providers - delegate to Pro plugin via action
601 do_action('formlayer_enqueue_captcha_scripts', $providers);
602 }
603
604 static function get_countries() {
605 return [
606 'AF' => 'Afghanistan',
607 'AL' => 'Albania',
608 'DZ' => 'Algeria',
609 'AS' => 'American Samoa',
610 'AD' => 'Andorra',
611 'AO' => 'Angola',
612 'AI' => 'Anguilla',
613 'AQ' => 'Antarctica',
614 'AG' => 'Antigua and Barbuda',
615 'AR' => 'Argentina',
616 'AM' => 'Armenia',
617 'AW' => 'Aruba',
618 'AU' => 'Australia',
619 'AT' => 'Austria',
620 'AZ' => 'Azerbaijan',
621 'BS' => 'Bahamas',
622 'BH' => 'Bahrain',
623 'BD' => 'Bangladesh',
624 'BB' => 'Barbados',
625 'BY' => 'Belarus',
626 'BE' => 'Belgium',
627 'BZ' => 'Belize',
628 'BJ' => 'Benin',
629 'BM' => 'Bermuda',
630 'BT' => 'Bhutan',
631 'BO' => 'Bolivia',
632 'BA' => 'Bosnia and Herzegovina',
633 'BW' => 'Botswana',
634 'BV' => 'Bouvet Island',
635 'BR' => 'Brazil',
636 'IO' => 'British Indian Ocean Territory',
637 'BN' => 'Brunei Darussalam',
638 'BG' => 'Bulgaria',
639 'BF' => 'Burkina Faso',
640 'BI' => 'Burundi',
641 'KH' => 'Cambodia',
642 'CM' => 'Cameroon',
643 'CA' => 'Canada',
644 'CV' => 'Cape Verde',
645 'KY' => 'Cayman Islands',
646 'CF' => 'Central African Republic',
647 'TD' => 'Chad',
648 'CL' => 'Chile',
649 'CN' => 'China',
650 'CX' => 'Christmas Island',
651 'CC' => 'Cocos (Keeling) Islands',
652 'CO' => 'Colombia',
653 'KM' => 'Comoros',
654 'CG' => 'Congo',
655 'CD' => 'Congo, the Democratic Republic of the',
656 'CK' => 'Cook Islands',
657 'CR' => 'Costa Rica',
658 'CI' => 'Cote d\'Ivoire',
659 'HR' => 'Croatia',
660 'CU' => 'Cuba',
661 'CY' => 'Cyprus',
662 'CZ' => 'Czech Republic',
663 'DK' => 'Denmark',
664 'DJ' => 'Djibouti',
665 'DM' => 'Dominica',
666 'DO' => 'Dominican Republic',
667 'EC' => 'Ecuador',
668 'EG' => 'Egypt',
669 'SV' => 'El Salvador',
670 'GQ' => 'Equatorial Guinea',
671 'ER' => 'Eritrea',
672 'EE' => 'Estonia',
673 'ET' => 'Ethiopia',
674 'FK' => 'Falkland Islands (Malvinas)',
675 'FO' => 'Faroe Islands',
676 'FJ' => 'Fiji',
677 'FI' => 'Finland',
678 'FR' => 'France',
679 'GF' => 'French Guiana',
680 'PF' => 'French Polynesia',
681 'TF' => 'French Southern Territories',
682 'GA' => 'Gabon',
683 'GM' => 'Gambia',
684 'GE' => 'Georgia',
685 'DE' => 'Germany',
686 'GH' => 'Ghana',
687 'GI' => 'Gibraltar',
688 'GR' => 'Greece',
689 'GL' => 'Greenland',
690 'GD' => 'Grenada',
691 'GP' => 'Guadeloupe',
692 'GU' => 'Guam',
693 'GT' => 'Guatemala',
694 'GN' => 'Guinea',
695 'GW' => 'Guinea-Bissau',
696 'GY' => 'Guyana',
697 'HT' => 'Haiti',
698 'HM' => 'Heard Island and McDonald Islands',
699 'VA' => 'Holy See (Vatican City State)',
700 'HN' => 'Honduras',
701 'HK' => 'Hong Kong',
702 'HU' => 'Hungary',
703 'IS' => 'Iceland',
704 'IN' => 'India',
705 'ID' => 'Indonesia',
706 'IR' => 'Iran, Islamic Republic of',
707 'IQ' => 'Iraq',
708 'IE' => 'Ireland',
709 'IL' => 'Israel',
710 'IT' => 'Italy',
711 'JM' => 'Jamaica',
712 'JP' => 'Japan',
713 'JO' => 'Jordan',
714 'KZ' => 'Kazakhstan',
715 'KE' => 'Kenya',
716 'KI' => 'Kiribati',
717 'KP' => 'Korea, Democratic People\'s Republic of',
718 'KR' => 'Korea, Republic of',
719 'KW' => 'Kuwait',
720 'KG' => 'Kyrgyzstan',
721 'LA' => 'Lao People\'s Democratic Republic',
722 'LV' => 'Latvia',
723 'LB' => 'Lebanon',
724 'LS' => 'Lesotho',
725 'LR' => 'Liberia',
726 'LY' => 'Libyan Arab Jamahiriya',
727 'LI' => 'Liechtenstein',
728 'LT' => 'Lithuania',
729 'LU' => 'Luxembourg',
730 'MO' => 'Macao',
731 'MK' => 'Macedonia, the former Yugoslav Republic of',
732 'MG' => 'Madagascar',
733 'MW' => 'Malawi',
734 'MY' => 'Malaysia',
735 'MV' => 'Maldives',
736 'ML' => 'Mali',
737 'MT' => 'Malta',
738 'MH' => 'Marshall Islands',
739 'MQ' => 'Martinique',
740 'MR' => 'Mauritania',
741 'MU' => 'Mauritius',
742 'YT' => 'Mayotte',
743 'MX' => 'Mexico',
744 'FM' => 'Micronesia, Federated States of',
745 'MD' => 'Moldova, Republic of',
746 'MC' => 'Monaco',
747 'MN' => 'Mongolia',
748 'MS' => 'Montserrat',
749 'MA' => 'Morocco',
750 'MZ' => 'Mozambique',
751 'MM' => 'Myanmar',
752 'NA' => 'Namibia',
753 'NR' => 'Nauru',
754 'NP' => 'Nepal',
755 'NL' => 'Netherlands',
756 'AN' => 'Netherlands Antilles',
757 'NC' => 'New Caledonia',
758 'NZ' => 'New Zealand',
759 'NI' => 'Nicaragua',
760 'NE' => 'Niger',
761 'NG' => 'Nigeria',
762 'NU' => 'Niue',
763 'NF' => 'Norfolk Island',
764 'MP' => 'Northern Mariana Islands',
765 'NO' => 'Norway',
766 'OM' => 'Oman',
767 'PK' => 'Pakistan',
768 'PW' => 'Palau',
769 'PS' => 'Palestinian Territory, Occupied',
770 'PA' => 'Panama',
771 'PG' => 'Papua New Guinea',
772 'PY' => 'Paraguay',
773 'PE' => 'Peru',
774 'PH' => 'Philippines',
775 'PN' => 'Pitcairn',
776 'PL' => 'Poland',
777 'PT' => 'Portugal',
778 'PR' => 'Puerto Rico',
779 'QA' => 'Qatar',
780 'RE' => 'Reunion',
781 'RO' => 'Romania',
782 'RU' => 'Russian Federation',
783 'RW' => 'Rwanda',
784 'SH' => 'Saint Helena',
785 'KN' => 'Saint Kitts and Nevis',
786 'LC' => 'Saint Lucia',
787 'PM' => 'Saint Pierre and Miquelon',
788 'VC' => 'Saint Vincent and the Grenadines',
789 'WS' => 'Samoa',
790 'SM' => 'San Marino',
791 'ST' => 'Sao Tome and Principe',
792 'SA' => 'Saudi Arabia',
793 'SN' => 'Senegal',
794 'CS' => 'Serbia and Montenegro',
795 'SC' => 'Seychelles',
796 'SL' => 'Sierra Leone',
797 'SG' => 'Singapore',
798 'SK' => 'Slovakia',
799 'SI' => 'Slovenia',
800 'SB' => 'Solomon Islands',
801 'SO' => 'Somalia',
802 'ZA' => 'South Africa',
803 'GS' => 'South Georgia and the South Sandwich Islands',
804 'ES' => 'Spain',
805 'LK' => 'Sri Lanka',
806 'SD' => 'Sudan',
807 'SR' => 'Suriname',
808 'SJ' => 'Svalbard and Jan Mayen',
809 'SZ' => 'Swaziland',
810 'SE' => 'Sweden',
811 'CH' => 'Switzerland',
812 'SY' => 'Syrian Arab Republic',
813 'TW' => 'Taiwan, Province of China',
814 'TJ' => 'Tajikistan',
815 'TZ' => 'Tanzania, United Republic of',
816 'TH' => 'Thailand',
817 'TL' => 'Timor-Leste',
818 'TG' => 'Togo',
819 'TK' => 'Tokelau',
820 'TO' => 'Tonga',
821 'TT' => 'Trinidad and Tobago',
822 'TN' => 'Tunisia',
823 'TR' => 'Turkey',
824 'TM' => 'Turkmenistan',
825 'TC' => 'Turks and Caicos Islands',
826 'TV' => 'Tuvalu',
827 'UG' => 'Uganda',
828 'UA' => 'Ukraine',
829 'AE' => 'United Arab Emirates',
830 'GB' => 'United Kingdom',
831 'US' => 'United States',
832 'UM' => 'United States Minor Outlying Islands',
833 'UY' => 'Uruguay',
834 'UZ' => 'Uzbekistan',
835 'VU' => 'Vanuatu',
836 'VE' => 'Venezuela',
837 'VN' => 'Viet Nam',
838 'VG' => 'Virgin Islands, British',
839 'VI' => 'Virgin Islands, U.S.',
840 'WF' => 'Wallis and Futuna',
841 'EH' => 'Western Sahara',
842 'YE' => 'Yemen',
843 'ZM' => 'Zambia',
844 'ZW' => 'Zimbabwe'
845 ];
846 }
847 }