PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.3
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.3
3.6.0 3.5.3 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 1 week ago class-evf-builder-integrations.php 2 months ago class-evf-builder-page.php 3 months ago class-evf-builder-payments.php 2 months ago class-evf-builder-settings.php 4 months ago
class-evf-builder-fields.php
836 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 // Repeater controls require the Pro plugin (EFP_VERSION); without it the addon
444 // does not register the repeater field, so the button must not be shown.
445 if ( defined( 'EVF_REPEATER_FIELDS_VERSION' ) && defined( 'EFP_VERSION' ) ) {
446 echo '<div class="evf-repeater-row-wrapper">'; // Repeater Row Wrapper starts.
447 }
448
449 $next_row_id = $row_ids ? max( $row_ids ) : 1;
450 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>';
451
452 if ( defined( 'EVF_REPEATER_FIELDS_VERSION' ) && defined( 'EFP_VERSION' ) ) {
453 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>';
454 echo '</div>'; // Repeater Row Wrapper ends.
455 }
456 echo '</div >';
457 }
458
459 /**
460 * Single Field preview.
461 *
462 * @param array $field Field data and settings.
463 * @param bool $show_actions Whether to render the edit action chrome
464 * (duplicate/delete/settings). Disabled for the
465 * read-only AI preview so it shows only field content.
466 */
467 public function field_preview( $field, $show_actions = true ) {
468 $css = ! empty( $field['size'] ) ? 'size-' . esc_attr( $field['size'] ) : '';
469 $css .= ! empty( $field['label_hide'] ) && '1' === $field['label_hide'] ? ' label_hide' : '';
470 $css .= ! empty( $field['sublabel_hide'] ) && '1' === $field['sublabel_hide'] ? ' sublabel_hide' : '';
471 $css .= ! empty( $field['required'] ) && '1' === $field['required'] ? ' required' : '';
472 $css .= ! empty( $field['input_columns'] ) && '2' === $field['input_columns'] ? ' everest-forms-list-2-columns' : '';
473 $css .= ! empty( $field['input_columns'] ) && '3' === $field['input_columns'] ? ' everest-forms-list-3-columns' : '';
474 $css .= ! empty( $field['input_columns'] ) && 'inline' === $field['input_columns'] ? ' everest-forms-list-inline' : '';
475 $is_locked = evf_is_field_locked( $field['type'] );
476
477 // Catch fields whose required addon is inactive even when not plan-locked
478 // (e.g. EVF Pro registers credit-card without is_pro, removing it from the
479 // normal lock check, but the Stripe addon may still be absent).
480 if ( ! $is_locked ) {
481 $is_locked = ! empty( evf_field_inactive_addon( $field['type'] ) );
482 }
483
484 if ( $is_locked ) {
485 $css .= ' everest-forms-field-locked';
486 $lock_data = $this->get_locked_field_trigger( $field['type'] );
487 if ( 'evf-upgrade-addon' === $lock_data['trigger'] ) {
488 $css .= ' everest-forms-field-locked-addon';
489 }
490 }
491 $css = apply_filters( 'everest_forms_field_preview_class', $css, $field );
492 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'] ) );
493 if ( $show_actions ) {
494 printf( '<div class="evf-field-action">' );
495 if ( 'repeater-fields' !== $field['type'] ) {
496 printf( '<a href="#" class="everest-forms-field-duplicate" title="%s"><span class="dashicons dashicons-media-default"></span></a>', esc_html__( 'Duplicate Field', 'everest-forms' ) );
497 printf( '<a href="#" class="everest-forms-field-delete" title="%s"><span class="dashicons dashicons-trash"></span></a>', esc_html__( 'Delete Field', 'everest-forms' ) );
498 printf( '<a href="#" class="everest-forms-field-setting" title="%s"><span class="dashicons dashicons-admin-generic"></span></a>', esc_html__( 'Settings', 'everest-forms' ) );
499 } else {
500 printf( '<a href="#" class="evf-duplicate-row" title="%s"><span class="dashicons dashicons-media-default"></span></a>', esc_html__( 'Duplicate Repeater', 'everest-forms' ) );
501 printf( '<a href="#" class="evf-delete-row" title="%s"><span class="dashicons dashicons-trash"></span></a>', esc_html__( 'Delete Repeater', 'everest-forms' ) );
502 }
503 printf( '</div>' );
504 }
505
506 // Locked (Pro/addon) fields placed in the form. The orange "Pro" icon is
507 // added inline next to the field label via CSS (.everest-forms-field-locked
508 // .label-title::after) — matching the fields sidebar — so no absolute badge
509 // element is emitted here.
510 if ( $is_locked ) {
511 // Render the field's real preview (the free plugin now bundles the
512 // builder-scope rendering for Pro fields). If a field still ships no
513 // preview, fall back to a representative card instead of a blank block.
514 ob_start();
515 do_action( 'everest_forms_builder_fields_preview_' . $field['type'], $field );
516 $preview = trim( ob_get_clean() );
517
518 if ( '' === $preview ) {
519 $this->locked_field_preview_body( $field );
520 } else {
521 echo $preview; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Field preview is escaped by the field class.
522 }
523 } else {
524 do_action( 'everest_forms_builder_fields_preview_' . $field['type'], $field );
525 }
526
527 echo '</div>';
528 }
529
530 /**
531 * Synthesize a representative preview body for a locked (Pro/addon) field.
532 *
533 * The free plugin ships Pro fields as stubs without a field_preview(), so on
534 * the builder canvas / AI preview we render a consistent upsell card showing
535 * the label and the field type name rather than an empty block.
536 *
537 * @param array $field Field data and settings.
538 */
539 public function locked_field_preview_body( $field ) {
540 $meta = evf()->form_fields->get_pro_fields_meta();
541 $type_name = isset( $meta[ $field['type'] ]['name'] ) ? $meta[ $field['type'] ]['name'] : $field['type'];
542 $label = ! empty( $field['label'] ) ? $field['label'] : $type_name;
543 $required = ! empty( $field['required'] ) && '1' === $field['required'];
544
545 // When the user is already licensed but the field's required add-on is
546 // inactive, prompt to activate the add-on instead of upselling Pro.
547 $trigger = $this->get_locked_field_trigger( $field['type'] );
548 if ( ! empty( $trigger['licensed'] ) && ! empty( $trigger['addon'] ) ) {
549 /* translators: %s: add-on name (e.g. Stripe). */
550 $message = sprintf( esc_html__( 'Activate the %s add-on to use this field', 'everest-forms' ), esc_html( $this->humanize_addon( $trigger['addon'] ) ) );
551 } else {
552 /* translators: %s: field type name (e.g. Signature). */
553 $message = sprintf( esc_html__( '%s is a premium field', 'everest-forms' ), esc_html( $type_name ) );
554 }
555 ?>
556 <label class="evf-locked-field-label">
557 <?php echo esc_html( $label ); ?>
558 <?php if ( $required ) : ?>
559 <span class="evf-locked-field-required">*</span>
560 <?php endif; ?>
561 </label>
562 <div class="evf-locked-field-placeholder">
563 <span class="dashicons dashicons-lock"></span>
564 <span><?php echo esc_html( $message ); ?></span>
565 </div>
566 <?php
567 }
568
569 /**
570 * Render a read-only, edit-chrome-free preview of a form's fields.
571 *
572 * Reuses the exact per-field markup of field_preview() (so the preview is
573 * pixel-identical to the builder canvas) but omits the row toolbars, grid
574 * selectors, add-row buttons and per-field action icons that only make sense
575 * inside the live builder. Used by the "Create with AI" preview endpoint.
576 *
577 * @param array $form_data Form data ( form_fields, structure ).
578 * @return string Preview HTML.
579 */
580 public function render_ai_preview( $form_data ) {
581 $fields = isset( $form_data['form_fields'] ) ? $form_data['form_fields'] : array();
582 $structure = isset( $form_data['structure'] ) && ! empty( $form_data['structure'] ) ? $form_data['structure'] : array();
583
584 // Fall back to one-field-per-row if no structure is provided.
585 if ( empty( $structure ) ) {
586 $row = 1;
587 foreach ( array_keys( $fields ) as $fid ) {
588 $structure[ 'row_' . $row ] = array( 'grid_1' => array( $fid ) );
589 $row++;
590 }
591 }
592
593 // Multi-part: build a row_key → 1-based part number map so React can
594 // show/hide rows per active tab (data-part-id attribute on each row div).
595 $is_multipart = isset( $form_data['settings']['enable_multi_part'] )
596 && evf_string_to_bool( $form_data['settings']['enable_multi_part'] );
597 $row_to_part = array();
598 if ( $is_multipart && ! empty( $form_data['multi_part'] ) ) {
599 foreach ( array_values( $form_data['multi_part'] ) as $part_idx => $part ) {
600 foreach ( ( $part['rows'] ?? array() ) as $row_key ) {
601 $row_to_part[ $row_key ] = $part_idx + 1; // 1-based
602 }
603 }
604 }
605
606 ob_start();
607 // Reproduce the builder's exact ancestor chain so the canvas field CSS
608 // (scoped to `#everest-forms-builder .evf-tab-content
609 // .everest-forms-panel-content-wrap .everest-forms-panel-content …`) applies
610 // identically in the AI preview. The preview stylesheet neutralises these
611 // containers' own layout (absolute positioning / sidebar width) so they
612 // don't disturb the preview pane. See .evf-ai-preview-canvas in
613 // assets/css/evf-locked-fields.css.
614 // Inline styles guarantee the builder container's own layout (absolute
615 // positioning / 100vh height) is neutralised regardless of stylesheet load
616 // order or caching, so the preview expands to its full content height.
617 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">';
618 echo '<div class="evf-admin-field-container"><div class="evf-admin-field-wrapper">';
619
620 $row_index = 0;
621 $current_part = 0; // tracks last assigned part, defaults rows to part 1 when no map entry
622 foreach ( $structure as $row_key => $row_data ) {
623 $grids = is_array( $row_data ) ? $row_data : array();
624 $active_grid = max( 1, count( $grids ) );
625
626 // Determine which part this row belongs to (default 1 for non-multipart forms).
627 if ( $is_multipart && ! empty( $row_to_part ) ) {
628 $part_id = $row_to_part[ $row_key ] ?? $current_part ?: 1;
629 $current_part = $part_id;
630 } else {
631 $part_id = 1;
632 }
633
634 // --evf-row-index drives a staggered "field appears" animation in the
635 // preview (see .evf-ai-preview-canvas in evf-locked-fields.css), so the
636 // form reveals field-by-field on generate / regenerate.
637 // data-part-id lets React show/hide rows when tabs are clicked.
638 printf(
639 '<div class="evf-admin-row" data-part-id="%d" style="--evf-row-index:%d;">',
640 absint( $part_id ),
641 absint( $row_index )
642 );
643 echo '<div class="evf-grid-lists">';
644 $row_index++;
645
646 $grid_index = 1;
647 foreach ( $grids as $grid ) {
648 printf( '<div class="evf-admin-grid evf-grid-%1$d" data-grid-id="%2$d">', absint( $active_grid ), absint( $grid_index ) );
649 foreach ( (array) $grid as $field_id ) {
650 if ( isset( $fields[ $field_id ] ) ) {
651 $this->field_preview( $fields[ $field_id ], false );
652 }
653 }
654 echo '</div>';
655 $grid_index++;
656 }
657
658 echo '</div>'; // .evf-grid-lists
659 echo '<div class="clear evf-clear"></div>';
660 echo '</div>'; // .evf-admin-row
661 }
662
663 echo '</div></div>'; // .evf-admin-field-wrapper .evf-admin-field-container
664 echo '</div></div></div></div>'; // .everest-forms-panel-content .everest-forms-panel-content-wrap .evf-tab-content #everest-forms-builder
665
666 return ob_get_clean();
667 }
668
669 /**
670 * Build the upgrade-trigger data attributes shared by the locked-field
671 * badge (canvas) and the locked-options overlay (settings panel).
672 *
673 * Reuses the existing upgrade.js handlers: when no license is present the
674 * generic "upgrade-modal" flow runs; when licensed but the required addon
675 * is inactive the "evf-upgrade-addon" install/activate flow runs.
676 *
677 * @param string $type Field type slug.
678 * @return array {
679 * @type string $trigger Trigger CSS class (upgrade-modal|evf-upgrade-addon).
680 * @type string $name Human-readable field name.
681 * @type string $attr Pre-escaped HTML data attributes.
682 * }
683 */
684 /**
685 * Find and return the registered field object for a given type slug, or null.
686 *
687 * @param string $type Field type slug.
688 * @return object|null
689 */
690 protected function get_field_object( $type ) {
691 foreach ( evf()->form_fields->form_fields() as $group ) {
692 foreach ( $group as $field_obj ) {
693 if ( $field_obj->type === $type ) {
694 return $field_obj;
695 }
696 }
697 }
698 return null;
699 }
700
701 protected function get_locked_field_trigger( $type ) {
702 $meta = evf()->form_fields->get_pro_fields_meta();
703 $info = isset( $meta[ $type ] ) ? $meta[ $type ] : array();
704 $addon = isset( $info['addon'] ) ? $info['addon'] : '';
705 $plan = isset( $info['plan'] ) ? $info['plan'] : '';
706 $links = isset( $info['links'] ) ? $info['links'] : array();
707 $name = isset( $info['name'] ) ? $info['name'] : $type;
708
709 // get_pro_fields_meta() only covers fields with is_pro = true. When EVF Pro
710 // registers the full field class (removing is_pro), the entry is absent.
711 // Fall back to the live field object so addon/name/plan are still resolved.
712 if ( empty( $addon ) ) {
713 $obj = $this->get_field_object( $type );
714 $addon = $obj && isset( $obj->addon ) ? $obj->addon : '';
715 if ( ( empty( $name ) || $name === $type ) && $obj && isset( $obj->name ) ) {
716 $name = $obj->name;
717 }
718 if ( empty( $plan ) && $obj && isset( $obj->plan ) ) {
719 $plan = $obj->plan;
720 }
721 }
722
723 // Last resort: check the addon-requirements filter. Covers fields whose
724 // class never registers when the addon is absent (e.g. credit-card bails
725 // in __construct() when Stripe is inactive), so get_field_object() returns
726 // null. Pro/addons hook everest_forms_field_addon_requirements to declare
727 // these dependencies from always-loaded code.
728 if ( empty( $addon ) ) {
729 $requirements = apply_filters( 'everest_forms_field_addon_requirements', array() );
730 $addon = isset( $requirements[ $type ] ) ? $requirements[ $type ] : '';
731 }
732
733 $licensed = false !== evf_get_license_plan();
734 // Route to the addon install/activate flow whenever the user is licensed
735 // (running EVF Pro) and the field declares a required addon — even when the
736 // field is still flagged is_pro (e.g. the free plugin bundles a Pro field as
737 // an is_pro stub with an addon slug, like repeater-fields). Showing the
738 // "Upgrade to Pro" modal in that case is misleading because Pro is already
739 // active; the user only needs to activate the field's addon. When the field
740 // has no addon (purely plan-locked) or the user is unlicensed, fall back to
741 // the upgrade-modal. The addon AJAX handler still verifies the license plan
742 // covers the addon and shows an upgrade-plan modal when it does not.
743 $trigger = ( $licensed && ! empty( $addon ) ) ? 'evf-upgrade-addon' : 'upgrade-modal';
744
745 $attr = sprintf(
746 ' 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"',
747 esc_attr( $type ),
748 esc_attr( $name ),
749 esc_attr( $plan ),
750 esc_attr( $addon ),
751 esc_attr( $trigger ),
752 esc_attr( wp_json_encode( $links ) )
753 );
754
755 return array(
756 'trigger' => $trigger,
757 'name' => $name,
758 'attr' => $attr,
759 'addon' => $addon,
760 'licensed' => $licensed,
761 );
762 }
763
764 /**
765 * Humanize an addon slug into a display name (e.g.
766 * "everest-forms-survey-polls-quiz" => "Survey Polls Quiz").
767 *
768 * @param string $slug Addon slug.
769 * @return string
770 */
771 protected function humanize_addon( $slug ) {
772 $slug = preg_replace( '/^everest-forms-/', '', (string) $slug );
773 return ucwords( str_replace( '-', ' ', $slug ) );
774 }
775
776 /**
777 * Output the PRO ribbon for a locked field on the builder canvas.
778 *
779 * @param string $type Field type slug.
780 */
781 public function locked_field_badge( $type ) {
782 $data = $this->get_locked_field_trigger( $type );
783
784 printf(
785 '<span class="everest-forms-field-pro-badge everest-forms-locked-field-cta %1$s"%2$s title="%3$s">%4$s</span>',
786 esc_attr( $data['trigger'] ),
787 $data['attr'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-escaped in get_locked_field_trigger().
788 esc_attr__( 'This is a premium field. Upgrade to use it on your form.', 'everest-forms' ),
789 esc_html__( 'PRO', 'everest-forms' )
790 );
791 }
792
793 /**
794 * Output the locked notice banner above a Pro field's settings panel.
795 *
796 * The field's real option rows are still rendered below this banner so all
797 * settings are READABLE; CSS (.everest-forms-field-option-locked) disables
798 * pointer events on them so they are not editable, while their inputs remain
799 * enabled (not the HTML `disabled` attribute) so the field data round-trips
800 * on save and the field works the moment the user upgrades.
801 *
802 * @param string $type Field type slug.
803 */
804 public function locked_field_option_overlay( $type ) {
805 $data = $this->get_locked_field_trigger( $type );
806 $needs_addon = ( 'evf-upgrade-addon' === $data['trigger'] ); // Licensed, but the required addon is inactive.
807 $field_strong = '<strong>' . esc_html( $data['name'] ) . '</strong>';
808
809 if ( $needs_addon && ! empty( $data['addon'] ) ) {
810 $addon_strong = '<strong>' . esc_html( $this->humanize_addon( $data['addon'] ) ) . '</strong>';
811 /* translators: 1: field name, 2: addon name. */
812 $message = sprintf( esc_html__( '%1$s needs the %2$s add-on. Activate it to use this field.', 'everest-forms' ), $field_strong, $addon_strong );
813 $cta_label = esc_html__( 'Activate add-on', 'everest-forms' );
814 $tag_label = esc_html__( 'ADD-ON', 'everest-forms' );
815 $tag_icon = 'dashicons-admin-plugins';
816 } else {
817 /* translators: %s: field name. */
818 $message = sprintf( esc_html__( '%s is a premium field — settings are read-only until you upgrade.', 'everest-forms' ), $field_strong );
819 $cta_label = esc_html__( 'Unlock', 'everest-forms' );
820 $tag_label = esc_html__( 'PRO', 'everest-forms' );
821 $tag_icon = 'dashicons-lock';
822 }
823 ?>
824 <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(). ?>>
825 <span class="evf-locked-banner-tag"><span class="dashicons <?php echo esc_attr( $tag_icon ); ?>"></span><?php echo esc_html( $tag_label ); ?></span>
826 <span class="evf-locked-banner-text">
827 <?php echo wp_kses( $message, array( 'strong' => array() ) ); ?>
828 </span>
829 <button type="button" class="everest-forms-btn everest-forms-btn-primary evf-locked-banner-cta"><?php echo esc_html( $cta_label ); ?></button>
830 </div>
831 <?php
832 }
833 }
834
835 return new EVF_Builder_Fields();
836