PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.2
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.2
3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / admin / builder / class-evf-builder-fields.php
everest-forms / includes / admin / builder Last commit date
class-evf-builder-fields.php 2 weeks ago class-evf-builder-integrations.php 2 weeks ago class-evf-builder-page.php 2 months ago class-evf-builder-payments.php 2 weeks ago class-evf-builder-settings.php 3 months ago
class-evf-builder-fields.php
834 lines
1 <?php
2 /**
3 * EverestForms Builder Fields
4 *
5 * @package EverestForms\Admin
6 * @since 1.2.0
7 */
8
9 defined( 'ABSPATH' ) || exit;
10
11 if ( class_exists( 'EVF_Builder_Fields', false ) ) {
12 return new EVF_Builder_Fields();
13 }
14
15 /**
16 * EVF_Builder_Fields class.
17 */
18 class EVF_Builder_Fields extends EVF_Builder_Page {
19
20 /**
21 * Contains information for multi-part forms.
22 *
23 * Forms that do not contain parts return false, otherwise returns an array
24 * that contains the number of total pages and page counter used when
25 * displaying part rows.
26 *
27 * @since 1.3.2
28 *
29 * @var array
30 */
31 public static $parts = array();
32
33 /**
34 * Constructor.
35 */
36 public function __construct() {
37 $this->id = 'fields';
38 $this->label = __( 'Fields', 'everest-forms' );
39 $this->sidebar = true;
40
41 parent::__construct();
42 }
43
44 /**
45 * Hook in tabs.
46 */
47 public function init_hooks() {
48 if ( is_object( $this->form ) ) {
49 add_action( 'everest_forms_builder_fields', array( $this, 'output_fields' ) );
50 add_action( 'everest_forms_builder_fields_options', array( $this, 'output_fields_options' ) );
51 add_action( 'everest_forms_builder_fields_preview', array( $this, 'output_fields_preview' ) );
52 }
53 }
54
55 /**
56 * Outputs the builder sidebar.
57 */
58 public function output_sidebar() {
59 ?>
60 <div class="everest-forms-fields-tab">
61 <a href="#" id="add-fields" class="fields active"><?php esc_html_e( 'Add Fields', 'everest-forms' ); ?></a>
62 <a href="#" id="field-options" class="options"><?php esc_html_e( 'Field Options', 'everest-forms' ); ?></a>
63 <?php do_action( 'everest_forms_builder_fields_tab', $this->form ); ?>
64 </div>
65 <div class="everest-forms-tab-content">
66 <div class="everest-forms-add-fields">
67 <div class="everest-forms-input-group everest-forms-search-input evf-mb-3">
68 <input id="everest-forms-search-fields" class="everest-forms-input-control everest-forms-search-fields" type="text" placeholder="<?php esc_attr_e( 'Search fields&hellip;', 'everest-forms' ); ?>" />
69 <div class="everest-forms-input-group__append">
70 <div class="everest-forms-input-group__text">
71 <svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 0 24 24" fill="#a1a4b9"><path d="M21.71,20.29,18,16.61A9,9,0,1,0,16.61,18l3.68,3.68a1,1,0,0,0,1.42,0A1,1,0,0,0,21.71,20.29ZM11,18a7,7,0,1,1,7-7A7,7,0,0,1,11,18Z"/></svg>
72 </div>
73 </div>
74 </div>
75 <div class="hidden everest-forms-fields-not-found">
76 <img src="<?php echo esc_attr( plugin_dir_url( EVF_PLUGIN_FILE ) . 'assets/images/fields-not-found.png' ); ?>" />
77 <h3 class="everest-forms-fields-not-found__title"><?php esc_html_e( 'Oops!', 'everest-forms' ); ?></h3>
78 <span><?php esc_html_e( 'There is not such field that you are searching for.', 'everest-forms' ); ?></span>
79 </div>
80 <?php do_action( 'everest_forms_builder_fields', $this->form ); ?>
81 </div>
82 <div class="everest-forms-field-options">
83 <?php do_action( 'everest_forms_builder_fields_options', $this->form ); ?>
84 </div>
85 <?php do_action( 'everest_forms_builder_fields_tab_content', $this->form ); ?>
86 </div>
87 <?php
88 }
89
90 /**
91 * Outputs the builder content.
92 */
93 public function output_content() {
94 ?>
95 <div class="everest-forms-preview-wrap">
96 <div class="everest-forms-preview">
97 <div class="everest-forms-title-desc">
98 <input id= "evf-edit-form-name" type="text" class="everest-forms-form-name everest-forms-name-input" value ="<?php echo isset( $this->form->post_title ) ? esc_html( $this->form->post_title ) : esc_html__( 'Form not found.', 'everest-forms' ); ?>" disabled autocomplete="off" required>
99 <span id="edit-form-name" class = "evf-icon dashicons dashicons-edit"></span>
100 </div>
101 <div class="everest-forms-field-wrap">
102 <?php do_action( 'everest_forms_builder_fields_preview', $this->form ); ?>
103 </div>
104 <?php evf_debug_data( $this->form_data ); ?>
105 </div>
106 </div>
107 <?php
108 }
109
110 /**
111 * Output fields group buttons.
112 */
113 public function output_fields() {
114 $containers = apply_filters(
115 'everest_forms_builder_layout_containers',
116 array(
117 array(
118 'type' => 'layout_one_col',
119 'label' => __( 'One Column', 'everest-forms' ),
120 'columns' => 1,
121 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.645 3.795a.28.28 0 0 0-.066-.181.21.21 0 0 0-.16-.076H6.581a.21.21 0 0 0-.16.076.28.28 0 0 0-.066.18v16.411c0 .068.024.133.066.181a.21.21 0 0 0 .16.076h10.838a.21.21 0 0 0 .16-.076.28.28 0 0 0 .066-.18zM19 20.205c0 .476-.167.933-.463 1.27a1.5 1.5 0 0 1-1.118.525H6.581c-.42 0-.821-.19-1.118-.526A1.93 1.93 0 0 1 5 20.205V3.795c0-.476.167-.933.463-1.27A1.5 1.5 0 0 1 6.581 2h10.838c.42 0 .822.19 1.118.526S19 3.319 19 3.795z"/></svg>',
122 ),
123 array(
124 'type' => 'layout_two_col',
125 'label' => __( 'Two Column', 'everest-forms' ),
126 'columns' => 2,
127 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.25 4a.25.25 0 0 0-.25-.25h-7.25v16.5H20a.25.25 0 0 0 .25-.25zM3.75 20a.25.25 0 0 0 .25.25h7.25V3.75H4a.25.25 0 0 0-.25.25zm18 0A1.75 1.75 0 0 1 20 21.75H4A1.75 1.75 0 0 1 2.25 20V4A1.75 1.75 0 0 1 4 2.25h16A1.75 1.75 0 0 1 21.75 4z"/></svg>',
128 ),
129 array(
130 'type' => 'layout_three_col',
131 'label' => __( 'Three Column', 'everest-forms' ),
132 'columns' => 3,
133 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.25 4a.25.25 0 0 0-.25-.25h-4.25v16.5H20a.25.25 0 0 0 .25-.25zM9.75 20.25h4.5V3.75h-4.5zm-6-.25a.25.25 0 0 0 .25.25h4.25V3.75H4a.25.25 0 0 0-.25.25zm18 0A1.75 1.75 0 0 1 20 21.75H4A1.75 1.75 0 0 1 2.25 20V4A1.75 1.75 0 0 1 4 2.25h16A1.75 1.75 0 0 1 21.75 4z"/></svg>',
134 ),
135 array(
136 'type' => 'layout_four_col',
137 'label' => __( 'Four Column', 'everest-forms' ),
138 'columns' => 4,
139 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.75 21V3a.75.75 0 0 1 1.5 0v18a.75.75 0 0 1-1.5 0m4.5 0V3a.75.75 0 0 1 1.5 0v18a.75.75 0 0 1-1.5 0m4.5 0V3a.75.75 0 0 1 1.5 0v18a.75.75 0 0 1-1.5 0"/><path d="M20.2 20.5V22H3.8v-1.5zm.3-.3V3.8a.3.3 0 0 0-.3-.3H3.8a.3.3 0 0 0-.3.3v16.4a.3.3 0 0 0 .3.3V22a1.8 1.8 0 0 1-1.79-1.616L2 20.2V3.8A1.8 1.8 0 0 1 3.8 2h16.4l.184.01A1.8 1.8 0 0 1 22 3.8v16.4l-.01.184a1.8 1.8 0 0 1-1.606 1.606L20.2 22v-1.5a.3.3 0 0 0 .3-.3"/></svg>',
140 ),
141 )
142 );
143
144 $form_fields = evf()->form_fields->form_fields();
145
146 $hide_legacy_payment_fields = ! defined( 'EFP_PLUGIN_FILE' );
147 $new_form_hidden_types = array( 'credit-card', 'authorize-net', 'square-payment' );
148
149 if ( ! empty( $form_fields ) ) {
150 foreach ( $form_fields as $group => $form_field ) {
151 ?>
152 <div class="everest-forms-add-fields-group open">
153 <a href="#" class="everest-forms-add-fields-heading" data-group="<?php echo esc_attr( $group ); ?>"><?php echo esc_html( evf_get_fields_group( $group ) ); ?><i class="handlediv"></i></a>
154 <div class="evf-registered-buttons">
155 <?php
156 foreach ( $form_field as $field ) :
157 if ( $hide_legacy_payment_fields && in_array( $field->type, $new_form_hidden_types, true ) ) {
158 continue;
159 }
160 $field_plan = isset( $field->plan ) ? $field->plan : '';
161 $addon_slug = isset( $field->addon ) ? $field->addon : '';
162 $field_links = isset( $field->links ) ? json_encode( $field->links ) : '';
163 ?>
164 <button type="button" id="everest-forms-add-fields-<?php echo esc_attr( $field->type ); ?>" class="evf-registered-item <?php echo sanitize_html_class( $field->class ); ?>" data-field-type="<?php echo esc_attr( $field->type ); ?>" data-field-plan="<?php echo esc_attr( $field_plan ); ?>" data-addon-slug="<?php echo esc_attr( $addon_slug ); ?>" data-links="<?php echo esc_attr( $field_links ); ?>">
165 <?php if ( isset( $field->icon ) ) : ?>
166 <i class="<?php echo esc_attr( $field->icon ); ?>"></i>
167 <?php endif; ?>
168 <?php echo esc_html( $field->name ); ?>
169 </button>
170 <?php endforeach; ?>
171 </div>
172 </div>
173 <?php
174
175 if ( 'advanced' === $group ) {
176 ?>
177 <div class="everest-forms-add-fields-group open evf-layout-group">
178 <a href="#" class="everest-forms-add-fields-heading" data-group="layout"><?php esc_html_e( 'Layout', 'everest-forms' ); ?><i class="handlediv"></i></a>
179 <div class="evf-registered-buttons">
180 <?php foreach ( $containers as $container ) : ?>
181 <button type="button"
182 class="evf-layout-container-btn"
183 data-columns="<?php echo absint( $container['columns'] ); ?>"
184 data-field-type="<?php echo esc_attr( $container['type'] ); ?>">
185 <?php echo $container['icon']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
186 <?php echo esc_html( $container['label'] ); ?>
187 </button>
188 <?php endforeach; ?>
189 </div>
190 </div>
191 <?php
192 }
193 }
194 }
195 }
196
197 /**
198 * Output fields setting options.
199 */
200 public function output_fields_options() {
201 $fields = isset( $this->form_data['form_fields'] ) ? $this->form_data['form_fields'] : array();
202 $recaptcha_type = get_option( 'everest_forms_recaptcha_type', 'v2' );
203 if ( isset( $this->form_data['settings']['recaptcha_support'] ) && '1' === $this->form_data['settings']['recaptcha_support'] ) {
204 if ( 'v2' === $recaptcha_type || 'v3' === $recaptcha_type ) {
205 $recaptcha_type = 'recaptcha';
206 }
207 $fields['IWX5HFxv2j-18'] = array(
208 'id' => 'IWX5HFxv2j-18',
209 'type' => $recaptcha_type,
210 'label' => '',
211 'meta-key' => $recaptcha_type . '_7543',
212 );
213 }
214
215 if ( ! empty( $fields ) ) {
216 foreach ( $fields as $field ) {
217 $is_locked = evf_is_field_locked( $field['type'] );
218
219 // Also treat fields whose required addon is inactive as locked — even
220 // when EVF Pro is active (which removes is_pro, bypassing the normal
221 // lock check). Without this, e.g. credit-card with Stripe absent
222 // renders a completely empty options panel with no CTA.
223 if ( ! $is_locked ) {
224 $is_locked = ! empty( evf_field_inactive_addon( $field['type'] ) );
225 }
226
227 $field_option_class = apply_filters(
228 'everest_forms_builder_field_option_class',
229 array(
230 'everest-forms-field-option',
231 'everest-forms-field-option-' . esc_attr( $field['type'] ),
232 ),
233 $field
234 );
235
236 // Locked Pro fields keep their settings rendered (inputs are NOT
237 // disabled so values persist on save) but are visually locked.
238 if ( $is_locked ) {
239 $field_option_class[] = 'everest-forms-field-option-locked';
240 }
241
242 ?>
243 <div class="<?php echo esc_attr( implode( ' ', $field_option_class ) ); ?>" id="everest-forms-field-option-<?php echo esc_attr( $field['id'] ); ?>" data-field-id="<?php echo esc_attr( $field['id'] ); ?>" >
244 <input type="hidden" name="form_fields[<?php echo esc_attr( $field['id'] ); ?>][id]" value="<?php echo esc_attr( $field['id'] ); ?>" class="everest-forms-field-option-hidden-id" />
245 <input type="hidden" name="form_fields[<?php echo esc_attr( $field['id'] ); ?>][type]" value="<?php echo esc_attr( $field['type'] ); ?>" class="everest-forms-field-option-hidden-type" />
246 <?php
247 if ( $is_locked ) {
248 $this->locked_field_option_overlay( $field['type'] );
249 }
250 do_action( 'everest_forms_builder_fields_options_' . $field['type'], $field );
251 ?>
252 </div>
253 <?php
254 }
255 } else {
256 printf( '<p class="no-fields">%s</p>', esc_html__( 'You don\'t have any fields yet.', 'everest-forms' ) );
257 }
258 }
259
260 /**
261 * Outputs fields preview content.
262 */
263 public function output_fields_preview() {
264 $form_data = $this->form_data;
265 $form_id = absint( $form_data['id'] );
266 $fields = isset( $form_data['form_fields'] ) ? $form_data['form_fields'] : array();
267 $structure = ! empty( $form_data['structure'] ) ? $form_data['structure'] : array( 'row_1' => array() );
268 $row_ids = array_map(
269 function ( $row_id ) {
270 return str_replace( 'row_', '', $row_id );
271 },
272 array_keys( $structure )
273 );
274
275 /**
276 * BW compatiable for multi-parts form.
277 *
278 * @todo Remove in Major EVF version 1.6.0
279 */
280 if ( defined( 'EVF_MULTI_PART_PLUGIN_FILE' ) ) {
281 include_once ABSPATH . 'wp-admin/includes/plugin.php';
282 $plugin_data = get_plugin_data( EVF_MULTI_PART_PLUGIN_FILE, false, false );
283
284 if ( version_compare( $plugin_data['Version'], '1.3.0', '<' ) ) {
285 $settings_defaults = array(
286 'indicator' => 'progress',
287 'indicator_color' => '#7e3bd0',
288 'nav_align' => 'center',
289 );
290
291 if ( isset( $form_data['settings']['enable_multi_part'] ) && evf_string_to_bool( $form_data['settings']['enable_multi_part'] ) ) {
292 $settings = isset( $form_data['settings']['multi_part'] ) ? $form_data['settings']['multi_part'] : array();
293
294 if ( ! empty( $form_data['multi_part'] ) ) {
295 self::$parts = array(
296 'total' => count( $form_data['multi_part'] ),
297 'current' => 1,
298 'parts' => array_values( $form_data['multi_part'] ),
299 'settings' => wp_parse_args( $settings, $settings_defaults ),
300 );
301 }
302 } else {
303 self::$parts = array(
304 'total' => '',
305 'current' => '',
306 'parts' => array(),
307 'settings' => $settings_defaults,
308 );
309 }
310 }
311 }
312
313 // Allow Multi-Part to be customized.
314 self::$parts[ $form_id ] = apply_filters( 'everest_forms_parts_data', self::$parts, $form_data, $form_id );
315
316 // Output the fields preview.
317 echo '<div class="evf-admin-field-container">';
318 echo '<div class="evf-admin-field-wrapper">';
319
320 /**
321 * Hook: everest_forms_display_builder_fields_before.
322 *
323 * @hooked EverestForms_MultiPart::display_builder_fields_before() Multi-Part markup open.
324 */
325 do_action( 'everest_forms_display_builder_fields_before', $form_data, $form_id );
326 if ( isset( $this->form_data['settings']['recaptcha_support'] ) && '1' === $this->form_data['settings']['recaptcha_support'] ) {
327 $num_rows = count( $structure );
328
329 // Create a new row with the next available row number.
330 $new_row_key = 'row_' . ( $num_rows + 1 );
331 $new_row = array(
332 $new_row_key => array(
333 'grid_1' => array(
334 'IWX5HFxv2j-18',
335 ),
336 ),
337 );
338 $structure = array_merge( $structure, $new_row );
339 }
340
341 foreach ( $structure as $row_id => $row_data ) {
342 $row = str_replace( 'row_', '', $row_id );
343 $row_grid = isset( $form_data['structure'][ 'row_' . $row ] ) ? $form_data['structure'][ 'row_' . $row ] : array();
344 $form_grid = apply_filters( 'everest_forms_default_form_grid', 4 );
345 $total_grid = $form_grid;
346 $active_grid = ( count( $row_grid ) > 0 ) ? count( $row_grid ) : ( isset( $this->form_data['settings']['recaptcha_support'] ) && '1' === $this->form_data['settings']['recaptcha_support'] ? 1 : 2 );
347 $active_grid = $active_grid > $total_grid ? $total_grid : $active_grid;
348
349 /**
350 * Hook: everest_forms_display_row_before.
351 */
352 do_action( 'everest_forms_display_builder_row_before', $row_id, $form_data, $form_id );
353
354 $repeater_field = apply_filters( 'everest_forms_display_repeater_fields', false, $row_grid, $fields );
355
356 echo '<div class="evf-admin-row" data-row-id="' . absint( $row ) . '"' . ( ! empty( $repeater_field ) ? esc_attr( $repeater_field ) : '' ) . '>';
357 echo '<div class="evf-toggle-row">';
358 if ( empty( $repeater_field ) ) {
359 echo '<div class="evf-duplicate-row"><span class="dashicons dashicons-media-default" title="Duplicate Row"></span></div>';
360 echo '<div class="evf-delete-row"><span class="dashicons dashicons-trash" title="Delete Row"></span></div>';
361 echo '<div class="evf-show-grid"><span class="dashicons dashicons-edit" title="Edit"></span></div>';
362 if ( defined( 'EFP_VERSION' ) ) {
363 echo '<div class="evf-row-setting"><span class="dashicons dashicons-admin-settings" title="Row Setting"></span></div>';
364 }
365 }
366 echo '<div class="evf-toggle-row-content">';
367 echo '<span>' . esc_html__( 'Row Settings', 'everest-forms' ) . '</span>';
368 echo '<small>' . esc_html__( 'Select the type of row', 'everest-forms' ) . '</small>';
369 echo '<div class="clear"></div>';
370
371 for ( $grid_active = 1; $grid_active <= $total_grid; $grid_active++ ) {
372 $class = 'evf-grid-selector';
373
374 if ( $grid_active === $active_grid ) {
375 $class .= ' active';
376 }
377
378 echo '<div class="' . esc_attr( $class ) . '" data-evf-grid="' . absint( $grid_active ) . '">';
379
380 $gaps = 15;
381 $width = ( 100 - $gaps ) / $grid_active;
382 $margin = ( $gaps / $grid_active ) / 2;
383
384 for ( $row_icon = 1; $row_icon <= $grid_active; $row_icon++ ) {
385 echo '<span style="width:' . (float) $width . '%; margin-left:' . (float) $margin . '%; margin-right:' . (float) $margin . '%"></span>';
386 }
387
388 echo '</div>';
389 }
390
391 echo '</div>';
392 echo '</div>';
393 echo '<div class="clear evf-clear"></div>';
394 echo '<div class="evf-grid-lists">';
395 $grid_class = 'evf-admin-grid evf-grid-' . ( $active_grid );
396 for ( $grid_start = 1; $grid_start <= $active_grid; $grid_start++ ) {
397 echo '<div class="' . esc_attr( $grid_class ) . ' " data-grid-id="' . absint( $grid_start ) . '">';
398 $grid_fields = isset( $row_grid[ 'grid_' . $grid_start ] ) && is_array( $row_grid[ 'grid_' . $grid_start ] ) ? $row_grid[ 'grid_' . $grid_start ] : ( isset( $this->form_data['settings']['recaptcha_support'] ) && '1' === $this->form_data['settings']['recaptcha_support'] ? array(
399 'IWX5HFxv2j-18',
400 ) : array() );
401 $recaptcha_type = get_option( 'everest_forms_recaptcha_type', 'v2' );
402 if ( isset( $this->form_data['settings']['recaptcha_support'] ) && '1' === $this->form_data['settings']['recaptcha_support'] ) {
403 if ( 'v2' === $recaptcha_type || 'v3' === $recaptcha_type ) {
404 $recaptcha_type = 'recaptcha';
405 }
406 $fields['IWX5HFxv2j-18'] = array(
407 'id' => 'IWX5HFxv2j-18',
408 'type' => $recaptcha_type,
409 'label' => '',
410 'meta-key' => $recaptcha_type . '_7543',
411 );
412 }
413 foreach ( $grid_fields as $field_id ) {
414 if ( isset( $fields[ $field_id ] ) ) {
415 // Locked Pro fields are rendered too (see field_preview()) so the
416 // builder can show them as an upsell instead of dropping them.
417 $this->field_preview( $fields[ $field_id ] );
418 }
419 }
420 echo '</div>';
421 }
422 echo '</div >';
423 echo '<div class="clear evf-clear"></div>';
424 echo '</div >';
425
426 /**
427 * Hook: everest_forms_display_builder_row_after.
428 *
429 * @hooked EverestForms_MultiPart::display_builder_row_after() Multi-Part markup (close previous part, open next).
430 */
431 do_action( 'everest_forms_display_builder_row_after', $row_id, $form_data, $form_id );
432 }
433
434 /**
435 * Hook: everest_forms_display_builder_fields_after.
436 *
437 * @hooked EverestForms_MultiPart::display_builder_fields_after() Multi-Part markup open.
438 */
439 do_action( 'everest_forms_display_builder_fields_after', $form_data, $form_id );
440
441 echo '</div>';
442 echo '<div class="clear evf-clear"></div>';
443 if ( defined( 'EVF_REPEATER_FIELDS_VERSION' ) ) {
444 echo '<div class="evf-repeater-row-wrapper">'; // Repeater Row Wrapper starts.
445 }
446
447 $next_row_id = $row_ids ? max( $row_ids ) : 1;
448 echo '<div class="evf-add-row" data-total-rows="' . count( $structure ) . '" data-next-row-id="' . (int) $next_row_id . '"><span class="everest-forms-btn everest-forms-btn-primary dashicons dashicons-plus-alt">' . esc_html__( 'Add Row', 'everest-forms' ) . '</span></div>';
449
450 if ( defined( 'EVF_REPEATER_FIELDS_VERSION' ) ) {
451 echo '<div class="evf-add-row repeater-row" data-total-rows="' . count( $structure ) . '" data-next-row-id="' . (int) $next_row_id . '"><span class="everest-forms-btn everest-forms-btn-primary dashicons dashicons-plus-alt">' . esc_html__( 'Add Repeater Row', 'everest-forms' ) . '</span></div>';
452 echo '</div>'; // Repeater Row Wrapper ends.
453 }
454 echo '</div >';
455 }
456
457 /**
458 * Single Field preview.
459 *
460 * @param array $field Field data and settings.
461 * @param bool $show_actions Whether to render the edit action chrome
462 * (duplicate/delete/settings). Disabled for the
463 * read-only AI preview so it shows only field content.
464 */
465 public function field_preview( $field, $show_actions = true ) {
466 $css = ! empty( $field['size'] ) ? 'size-' . esc_attr( $field['size'] ) : '';
467 $css .= ! empty( $field['label_hide'] ) && '1' === $field['label_hide'] ? ' label_hide' : '';
468 $css .= ! empty( $field['sublabel_hide'] ) && '1' === $field['sublabel_hide'] ? ' sublabel_hide' : '';
469 $css .= ! empty( $field['required'] ) && '1' === $field['required'] ? ' required' : '';
470 $css .= ! empty( $field['input_columns'] ) && '2' === $field['input_columns'] ? ' everest-forms-list-2-columns' : '';
471 $css .= ! empty( $field['input_columns'] ) && '3' === $field['input_columns'] ? ' everest-forms-list-3-columns' : '';
472 $css .= ! empty( $field['input_columns'] ) && 'inline' === $field['input_columns'] ? ' everest-forms-list-inline' : '';
473 $is_locked = evf_is_field_locked( $field['type'] );
474
475 // Catch fields whose required addon is inactive even when not plan-locked
476 // (e.g. EVF Pro registers credit-card without is_pro, removing it from the
477 // normal lock check, but the Stripe addon may still be absent).
478 if ( ! $is_locked ) {
479 $is_locked = ! empty( evf_field_inactive_addon( $field['type'] ) );
480 }
481
482 if ( $is_locked ) {
483 $css .= ' everest-forms-field-locked';
484 $lock_data = $this->get_locked_field_trigger( $field['type'] );
485 if ( 'evf-upgrade-addon' === $lock_data['trigger'] ) {
486 $css .= ' everest-forms-field-locked-addon';
487 }
488 }
489 $css = apply_filters( 'everest_forms_field_preview_class', $css, $field );
490 printf( '<div class="everest-forms-field everest-forms-field-%1$s %2$s" id="everest-forms-field-%3$s" data-field-id="%3$s" data-field-type="%4$s">', esc_attr( $field['type'] ), esc_attr( $css ), esc_attr( $field['id'] ), esc_attr( $field['type'] ) );
491 if ( $show_actions ) {
492 printf( '<div class="evf-field-action">' );
493 if ( 'repeater-fields' !== $field['type'] ) {
494 printf( '<a href="#" class="everest-forms-field-duplicate" title="%s"><span class="dashicons dashicons-media-default"></span></a>', esc_html__( 'Duplicate Field', 'everest-forms' ) );
495 printf( '<a href="#" class="everest-forms-field-delete" title="%s"><span class="dashicons dashicons-trash"></span></a>', esc_html__( 'Delete Field', 'everest-forms' ) );
496 printf( '<a href="#" class="everest-forms-field-setting" title="%s"><span class="dashicons dashicons-admin-generic"></span></a>', esc_html__( 'Settings', 'everest-forms' ) );
497 } else {
498 printf( '<a href="#" class="evf-duplicate-row" title="%s"><span class="dashicons dashicons-media-default"></span></a>', esc_html__( 'Duplicate Repeater', 'everest-forms' ) );
499 printf( '<a href="#" class="evf-delete-row" title="%s"><span class="dashicons dashicons-trash"></span></a>', esc_html__( 'Delete Repeater', 'everest-forms' ) );
500 }
501 printf( '</div>' );
502 }
503
504 // Locked (Pro/addon) fields placed in the form. The orange "Pro" icon is
505 // added inline next to the field label via CSS (.everest-forms-field-locked
506 // .label-title::after) — matching the fields sidebar — so no absolute badge
507 // element is emitted here.
508 if ( $is_locked ) {
509 // Render the field's real preview (the free plugin now bundles the
510 // builder-scope rendering for Pro fields). If a field still ships no
511 // preview, fall back to a representative card instead of a blank block.
512 ob_start();
513 do_action( 'everest_forms_builder_fields_preview_' . $field['type'], $field );
514 $preview = trim( ob_get_clean() );
515
516 if ( '' === $preview ) {
517 $this->locked_field_preview_body( $field );
518 } else {
519 echo $preview; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Field preview is escaped by the field class.
520 }
521 } else {
522 do_action( 'everest_forms_builder_fields_preview_' . $field['type'], $field );
523 }
524
525 echo '</div>';
526 }
527
528 /**
529 * Synthesize a representative preview body for a locked (Pro/addon) field.
530 *
531 * The free plugin ships Pro fields as stubs without a field_preview(), so on
532 * the builder canvas / AI preview we render a consistent upsell card showing
533 * the label and the field type name rather than an empty block.
534 *
535 * @param array $field Field data and settings.
536 */
537 public function locked_field_preview_body( $field ) {
538 $meta = evf()->form_fields->get_pro_fields_meta();
539 $type_name = isset( $meta[ $field['type'] ]['name'] ) ? $meta[ $field['type'] ]['name'] : $field['type'];
540 $label = ! empty( $field['label'] ) ? $field['label'] : $type_name;
541 $required = ! empty( $field['required'] ) && '1' === $field['required'];
542
543 // When the user is already licensed but the field's required add-on is
544 // inactive, prompt to activate the add-on instead of upselling Pro.
545 $trigger = $this->get_locked_field_trigger( $field['type'] );
546 if ( ! empty( $trigger['licensed'] ) && ! empty( $trigger['addon'] ) ) {
547 /* translators: %s: add-on name (e.g. Stripe). */
548 $message = sprintf( esc_html__( 'Activate the %s add-on to use this field', 'everest-forms' ), esc_html( $this->humanize_addon( $trigger['addon'] ) ) );
549 } else {
550 /* translators: %s: field type name (e.g. Signature). */
551 $message = sprintf( esc_html__( '%s is a premium field', 'everest-forms' ), esc_html( $type_name ) );
552 }
553 ?>
554 <label class="evf-locked-field-label">
555 <?php echo esc_html( $label ); ?>
556 <?php if ( $required ) : ?>
557 <span class="evf-locked-field-required">*</span>
558 <?php endif; ?>
559 </label>
560 <div class="evf-locked-field-placeholder">
561 <span class="dashicons dashicons-lock"></span>
562 <span><?php echo esc_html( $message ); ?></span>
563 </div>
564 <?php
565 }
566
567 /**
568 * Render a read-only, edit-chrome-free preview of a form's fields.
569 *
570 * Reuses the exact per-field markup of field_preview() (so the preview is
571 * pixel-identical to the builder canvas) but omits the row toolbars, grid
572 * selectors, add-row buttons and per-field action icons that only make sense
573 * inside the live builder. Used by the "Create with AI" preview endpoint.
574 *
575 * @param array $form_data Form data ( form_fields, structure ).
576 * @return string Preview HTML.
577 */
578 public function render_ai_preview( $form_data ) {
579 $fields = isset( $form_data['form_fields'] ) ? $form_data['form_fields'] : array();
580 $structure = isset( $form_data['structure'] ) && ! empty( $form_data['structure'] ) ? $form_data['structure'] : array();
581
582 // Fall back to one-field-per-row if no structure is provided.
583 if ( empty( $structure ) ) {
584 $row = 1;
585 foreach ( array_keys( $fields ) as $fid ) {
586 $structure[ 'row_' . $row ] = array( 'grid_1' => array( $fid ) );
587 $row++;
588 }
589 }
590
591 // Multi-part: build a row_key → 1-based part number map so React can
592 // show/hide rows per active tab (data-part-id attribute on each row div).
593 $is_multipart = isset( $form_data['settings']['enable_multi_part'] )
594 && evf_string_to_bool( $form_data['settings']['enable_multi_part'] );
595 $row_to_part = array();
596 if ( $is_multipart && ! empty( $form_data['multi_part'] ) ) {
597 foreach ( array_values( $form_data['multi_part'] ) as $part_idx => $part ) {
598 foreach ( ( $part['rows'] ?? array() ) as $row_key ) {
599 $row_to_part[ $row_key ] = $part_idx + 1; // 1-based
600 }
601 }
602 }
603
604 ob_start();
605 // Reproduce the builder's exact ancestor chain so the canvas field CSS
606 // (scoped to `#everest-forms-builder .evf-tab-content
607 // .everest-forms-panel-content-wrap .everest-forms-panel-content …`) applies
608 // identically in the AI preview. The preview stylesheet neutralises these
609 // containers' own layout (absolute positioning / sidebar width) so they
610 // don't disturb the preview pane. See .evf-ai-preview-canvas in
611 // assets/css/evf-locked-fields.css.
612 // Inline styles guarantee the builder container's own layout (absolute
613 // positioning / 100vh height) is neutralised regardless of stylesheet load
614 // order or caching, so the preview expands to its full content height.
615 echo '<div id="everest-forms-builder" style="position:static !important;inset:auto !important;min-height:0 !important;height:auto !important;width:100% !important;"><div class="evf-tab-content"><div class="everest-forms-panel-content-wrap"><div class="everest-forms-panel-content">';
616 echo '<div class="evf-admin-field-container"><div class="evf-admin-field-wrapper">';
617
618 $row_index = 0;
619 $current_part = 0; // tracks last assigned part, defaults rows to part 1 when no map entry
620 foreach ( $structure as $row_key => $row_data ) {
621 $grids = is_array( $row_data ) ? $row_data : array();
622 $active_grid = max( 1, count( $grids ) );
623
624 // Determine which part this row belongs to (default 1 for non-multipart forms).
625 if ( $is_multipart && ! empty( $row_to_part ) ) {
626 $part_id = $row_to_part[ $row_key ] ?? $current_part ?: 1;
627 $current_part = $part_id;
628 } else {
629 $part_id = 1;
630 }
631
632 // --evf-row-index drives a staggered "field appears" animation in the
633 // preview (see .evf-ai-preview-canvas in evf-locked-fields.css), so the
634 // form reveals field-by-field on generate / regenerate.
635 // data-part-id lets React show/hide rows when tabs are clicked.
636 printf(
637 '<div class="evf-admin-row" data-part-id="%d" style="--evf-row-index:%d;">',
638 absint( $part_id ),
639 absint( $row_index )
640 );
641 echo '<div class="evf-grid-lists">';
642 $row_index++;
643
644 $grid_index = 1;
645 foreach ( $grids as $grid ) {
646 printf( '<div class="evf-admin-grid evf-grid-%1$d" data-grid-id="%2$d">', absint( $active_grid ), absint( $grid_index ) );
647 foreach ( (array) $grid as $field_id ) {
648 if ( isset( $fields[ $field_id ] ) ) {
649 $this->field_preview( $fields[ $field_id ], false );
650 }
651 }
652 echo '</div>';
653 $grid_index++;
654 }
655
656 echo '</div>'; // .evf-grid-lists
657 echo '<div class="clear evf-clear"></div>';
658 echo '</div>'; // .evf-admin-row
659 }
660
661 echo '</div></div>'; // .evf-admin-field-wrapper .evf-admin-field-container
662 echo '</div></div></div></div>'; // .everest-forms-panel-content .everest-forms-panel-content-wrap .evf-tab-content #everest-forms-builder
663
664 return ob_get_clean();
665 }
666
667 /**
668 * Build the upgrade-trigger data attributes shared by the locked-field
669 * badge (canvas) and the locked-options overlay (settings panel).
670 *
671 * Reuses the existing upgrade.js handlers: when no license is present the
672 * generic "upgrade-modal" flow runs; when licensed but the required addon
673 * is inactive the "evf-upgrade-addon" install/activate flow runs.
674 *
675 * @param string $type Field type slug.
676 * @return array {
677 * @type string $trigger Trigger CSS class (upgrade-modal|evf-upgrade-addon).
678 * @type string $name Human-readable field name.
679 * @type string $attr Pre-escaped HTML data attributes.
680 * }
681 */
682 /**
683 * Find and return the registered field object for a given type slug, or null.
684 *
685 * @param string $type Field type slug.
686 * @return object|null
687 */
688 protected function get_field_object( $type ) {
689 foreach ( evf()->form_fields->form_fields() as $group ) {
690 foreach ( $group as $field_obj ) {
691 if ( $field_obj->type === $type ) {
692 return $field_obj;
693 }
694 }
695 }
696 return null;
697 }
698
699 protected function get_locked_field_trigger( $type ) {
700 $meta = evf()->form_fields->get_pro_fields_meta();
701 $info = isset( $meta[ $type ] ) ? $meta[ $type ] : array();
702 $addon = isset( $info['addon'] ) ? $info['addon'] : '';
703 $plan = isset( $info['plan'] ) ? $info['plan'] : '';
704 $links = isset( $info['links'] ) ? $info['links'] : array();
705 $name = isset( $info['name'] ) ? $info['name'] : $type;
706
707 // get_pro_fields_meta() only covers fields with is_pro = true. When EVF Pro
708 // registers the full field class (removing is_pro), the entry is absent.
709 // Fall back to the live field object so addon/name/plan are still resolved.
710 if ( empty( $addon ) ) {
711 $obj = $this->get_field_object( $type );
712 $addon = $obj && isset( $obj->addon ) ? $obj->addon : '';
713 if ( ( empty( $name ) || $name === $type ) && $obj && isset( $obj->name ) ) {
714 $name = $obj->name;
715 }
716 if ( empty( $plan ) && $obj && isset( $obj->plan ) ) {
717 $plan = $obj->plan;
718 }
719 }
720
721 // Last resort: check the addon-requirements filter. Covers fields whose
722 // class never registers when the addon is absent (e.g. credit-card bails
723 // in __construct() when Stripe is inactive), so get_field_object() returns
724 // null. Pro/addons hook everest_forms_field_addon_requirements to declare
725 // these dependencies from always-loaded code.
726 if ( empty( $addon ) ) {
727 $requirements = apply_filters( 'everest_forms_field_addon_requirements', array() );
728 $addon = isset( $requirements[ $type ] ) ? $requirements[ $type ] : '';
729 }
730
731 $licensed = false !== evf_get_license_plan();
732 // Route to the addon install/activate flow whenever the user is licensed
733 // (running EVF Pro) and the field declares a required addon — even when the
734 // field is still flagged is_pro (e.g. the free plugin bundles a Pro field as
735 // an is_pro stub with an addon slug, like repeater-fields). Showing the
736 // "Upgrade to Pro" modal in that case is misleading because Pro is already
737 // active; the user only needs to activate the field's addon. When the field
738 // has no addon (purely plan-locked) or the user is unlicensed, fall back to
739 // the upgrade-modal. The addon AJAX handler still verifies the license plan
740 // covers the addon and shows an upgrade-plan modal when it does not.
741 $trigger = ( $licensed && ! empty( $addon ) ) ? 'evf-upgrade-addon' : 'upgrade-modal';
742
743 $attr = sprintf(
744 ' data-field-type="%1$s" data-field-name="%2$s" data-field-plan="%3$s" data-addon-slug="%4$s" data-field-class="%5$s" data-links="%6$s"',
745 esc_attr( $type ),
746 esc_attr( $name ),
747 esc_attr( $plan ),
748 esc_attr( $addon ),
749 esc_attr( $trigger ),
750 esc_attr( wp_json_encode( $links ) )
751 );
752
753 return array(
754 'trigger' => $trigger,
755 'name' => $name,
756 'attr' => $attr,
757 'addon' => $addon,
758 'licensed' => $licensed,
759 );
760 }
761
762 /**
763 * Humanize an addon slug into a display name (e.g.
764 * "everest-forms-survey-polls-quiz" => "Survey Polls Quiz").
765 *
766 * @param string $slug Addon slug.
767 * @return string
768 */
769 protected function humanize_addon( $slug ) {
770 $slug = preg_replace( '/^everest-forms-/', '', (string) $slug );
771 return ucwords( str_replace( '-', ' ', $slug ) );
772 }
773
774 /**
775 * Output the PRO ribbon for a locked field on the builder canvas.
776 *
777 * @param string $type Field type slug.
778 */
779 public function locked_field_badge( $type ) {
780 $data = $this->get_locked_field_trigger( $type );
781
782 printf(
783 '<span class="everest-forms-field-pro-badge everest-forms-locked-field-cta %1$s"%2$s title="%3$s">%4$s</span>',
784 esc_attr( $data['trigger'] ),
785 $data['attr'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-escaped in get_locked_field_trigger().
786 esc_attr__( 'This is a premium field. Upgrade to use it on your form.', 'everest-forms' ),
787 esc_html__( 'PRO', 'everest-forms' )
788 );
789 }
790
791 /**
792 * Output the locked notice banner above a Pro field's settings panel.
793 *
794 * The field's real option rows are still rendered below this banner so all
795 * settings are READABLE; CSS (.everest-forms-field-option-locked) disables
796 * pointer events on them so they are not editable, while their inputs remain
797 * enabled (not the HTML `disabled` attribute) so the field data round-trips
798 * on save and the field works the moment the user upgrades.
799 *
800 * @param string $type Field type slug.
801 */
802 public function locked_field_option_overlay( $type ) {
803 $data = $this->get_locked_field_trigger( $type );
804 $needs_addon = ( 'evf-upgrade-addon' === $data['trigger'] ); // Licensed, but the required addon is inactive.
805 $field_strong = '<strong>' . esc_html( $data['name'] ) . '</strong>';
806
807 if ( $needs_addon && ! empty( $data['addon'] ) ) {
808 $addon_strong = '<strong>' . esc_html( $this->humanize_addon( $data['addon'] ) ) . '</strong>';
809 /* translators: 1: field name, 2: addon name. */
810 $message = sprintf( esc_html__( '%1$s needs the %2$s add-on. Activate it to use this field.', 'everest-forms' ), $field_strong, $addon_strong );
811 $cta_label = esc_html__( 'Activate add-on', 'everest-forms' );
812 $tag_label = esc_html__( 'ADD-ON', 'everest-forms' );
813 $tag_icon = 'dashicons-admin-plugins';
814 } else {
815 /* translators: %s: field name. */
816 $message = sprintf( esc_html__( '%s is a premium field — settings are read-only until you upgrade.', 'everest-forms' ), $field_strong );
817 $cta_label = esc_html__( 'Unlock', 'everest-forms' );
818 $tag_label = esc_html__( 'PRO', 'everest-forms' );
819 $tag_icon = 'dashicons-lock';
820 }
821 ?>
822 <div class="everest-forms-field-option-locked-banner everest-forms-locked-field-cta <?php echo esc_attr( $data['trigger'] ); ?>"<?php echo $data['attr']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-escaped in get_locked_field_trigger(). ?>>
823 <span class="evf-locked-banner-tag"><span class="dashicons <?php echo esc_attr( $tag_icon ); ?>"></span><?php echo esc_html( $tag_label ); ?></span>
824 <span class="evf-locked-banner-text">
825 <?php echo wp_kses( $message, array( 'strong' => array() ) ); ?>
826 </span>
827 <button type="button" class="everest-forms-btn everest-forms-btn-primary evf-locked-banner-cta"><?php echo esc_html( $cta_label ); ?></button>
828 </div>
829 <?php
830 }
831 }
832
833 return new EVF_Builder_Fields();
834