';
}
}
/**
* Render flexible content field
*
* @param array $field Field configuration.
* @param mixed $value Field value.
* @return void
*/
private function render_flexible_content_field( $field, $value ) {
$layouts = isset( $field['layouts'] ) ? $field['layouts'] : array();
$button_label = isset( $field['button_label'] ) ? $field['button_label'] : __( 'Add Row', 'airy-frontend-forms' );
$min = isset( $field['min'] ) ? absint( $field['min'] ) : 0;
$max = isset( $field['max'] ) ? absint( $field['max'] ) : 0;
if ( empty( $layouts ) ) {
echo '
' . esc_html__( 'No layouts configured for this flexible content field.', 'airy-frontend-forms' ) . '
';
return;
}
// Normalize value to array.
$rows = is_array( $value ) ? $value : array();
echo '
';
// Rows container.
echo '
';
if ( ! empty( $rows ) ) {
foreach ( $rows as $index => $row ) {
$layout_name = isset( $row['acf_fc_layout'] ) ? $row['acf_fc_layout'] : '';
$layout = $this->get_flexible_layout( $layouts, $layout_name );
if ( $layout ) {
$this->render_flexible_content_row( $field, $layout, $row, $index );
}
}
}
echo '
';
// Add row buttons.
echo '
';
echo '
' . esc_html( $button_label ) . '
';
echo '
';
foreach ( $layouts as $layout ) {
$layout_label = isset( $layout['label'] ) ? $layout['label'] : $layout['name'];
echo '';
echo esc_html( $layout_label );
echo ' ';
}
echo '
';
echo '
';
// Layout templates for JavaScript - use separate script tags for each layout.
echo '
';
foreach ( $layouts as $layout ) {
echo '';
}
echo '
';
echo '
';
}
/**
* Get flexible content layout by name
*
* @param array $layouts All layouts.
* @param string $name Layout name.
* @return array|null Layout or null if not found.
*/
private function get_flexible_layout( $layouts, $name ) {
foreach ( $layouts as $layout ) {
if ( isset( $layout['name'] ) && $layout['name'] === $name ) {
return $layout;
}
}
return null;
}
/**
* Render a flexible content row
*
* @param array $field Parent field configuration.
* @param array $layout Layout configuration.
* @param array $row_value Row values.
* @param int|string $index Row index.
* @return void
*/
private function render_flexible_content_row( $field, $layout, $row_value, $index ) {
$layout_label = isset( $layout['label'] ) ? $layout['label'] : $layout['name'];
$sub_fields = isset( $layout['sub_fields'] ) ? $layout['sub_fields'] : array();
echo '
';
// Row header.
echo '';
// Row content.
echo '
';
// Hidden input for layout type.
echo '
';
// Render sub-fields.
foreach ( $sub_fields as $sub_field ) {
$sub_field_name = $field['key'] . '[' . $index . '][' . $sub_field['name'] . ']';
$sub_field_value = isset( $row_value[ $sub_field['name'] ] ) ? $row_value[ $sub_field['name'] ] : '';
$sub_required = ! empty( $sub_field['required'] );
echo '
';
// Label.
$sub_label = isset( $sub_field['label'] ) ? $sub_field['label'] : $sub_field['name'];
echo '' . esc_html( $sub_label );
if ( $sub_required ) {
echo ' * ';
}
echo ' ';
// Render sub-field.
$this->render_sub_field( $sub_field, $sub_field_name, $sub_field_value, $sub_required );
echo '
';
}
echo '
';
echo '
';
}
/**
* Render nested flexible content row (for flexible content inside groups/repeaters)
*
* @param string $field_name Parent field name.
* @param array $layout Layout configuration.
* @param array $row_value Row values.
* @param int|string $index Row index.
* @return void
*/
private function render_nested_flexible_row( $field_name, $layout, $row_value, $index ) {
$layout_label = isset( $layout['label'] ) ? $layout['label'] : $layout['name'];
$sub_fields = isset( $layout['sub_fields'] ) ? $layout['sub_fields'] : array();
echo '
';
// Row header.
echo '';
// Row content.
echo '
';
// Hidden input for layout type.
echo '
';
// Render sub-fields.
foreach ( $sub_fields as $sub_field ) {
$sub_field_name = $field_name . '[' . $index . '][' . $sub_field['name'] . ']';
$sub_field_value = isset( $row_value[ $sub_field['name'] ] ) ? $row_value[ $sub_field['name'] ] : '';
$sub_required = ! empty( $sub_field['required'] );
echo '
';
// Label.
$sub_label = isset( $sub_field['label'] ) ? $sub_field['label'] : $sub_field['name'];
echo '' . esc_html( $sub_label );
if ( $sub_required ) {
echo ' * ';
}
echo ' ';
// Render sub-field.
$this->render_sub_field( $sub_field, $sub_field_name, $sub_field_value, $sub_required );
echo '
';
}
echo '
';
echo '
';
}
/**
* Render nav menu field
*
* @param array $field Field configuration.
* @param mixed $value Field value.
* @param bool $required Whether field is required.
* @return void
*/
private function render_nav_menu_field( $field, $value, $required ) {
$allow_null = isset( $field['allow_null'] ) ? $field['allow_null'] : true;
// Get all navigation menus.
$menus = wp_get_nav_menus();
if ( empty( $menus ) ) {
echo '
' . esc_html__( 'No navigation menus available.', 'airy-frontend-forms' ) . '
';
return;
}
// Normalize value.
$selected_id = 0;
if ( ! empty( $value ) ) {
if ( is_numeric( $value ) ) {
$selected_id = absint( $value );
} elseif ( is_object( $value ) && isset( $value->term_id ) ) {
$selected_id = absint( $value->term_id );
}
}
echo '';
}
/**
* Render icon picker field
*
* @param array $field Field configuration.
* @param mixed $value Field value.
* @param bool $required Whether field is required.
* @return void
*/
private function render_icon_picker_field( $field, $value, $required ) {
$field_key = esc_attr( $field['key'] );
$unique_id = 'affcf-iconpicker-' . wp_unique_id();
echo '
';
// Selected icon display area.
echo '
';
echo '
';
if ( ! empty( $value ) ) {
echo ' ';
} else {
echo '' . esc_html__( 'No icon selected', 'airy-frontend-forms' ) . ' ';
}
echo '
';
echo '
' . esc_html__( 'Select Icon', 'airy-frontend-forms' ) . ' ';
if ( ! empty( $value ) ) {
echo '
× ';
}
echo '
';
// Hidden input to store value.
echo '
';
// Icon picker dropdown/modal.
echo '
';
// Tabs: Dashicons / URL.
echo '
';
// Search bar.
echo '
';
echo ' ';
echo '
';
// Dashicons grid content.
echo '
';
echo '
';
$dashicons = $this->get_dashicons_list();
foreach ( $dashicons as $icon ) {
$selected_class = ( $value === $icon ) ? ' selected' : '';
$icon_name = str_replace( 'dashicons-', '', $icon );
echo '
';
echo ' ';
echo '
';
}
echo '
'; // grid.
echo '
'; // dashicons content.
// URL tab content.
echo '
';
echo '
';
echo ' ';
echo '' . esc_html__( 'Apply', 'airy-frontend-forms' ) . ' ';
echo '
';
echo '
';
echo '
'; // url content.
echo '
'; // modal.
echo '
'; // wrapper.
}
/**
* Get list of WordPress dashicons
*
* @return array List of dashicon class names.
*/
private function get_dashicons_list() {
return array(
// Admin Menu.
'dashicons-admin-appearance',
'dashicons-admin-collapse',
'dashicons-admin-comments',
'dashicons-admin-customizer',
'dashicons-admin-generic',
'dashicons-admin-home',
'dashicons-admin-links',
'dashicons-admin-media',
'dashicons-admin-multisite',
'dashicons-admin-network',
'dashicons-admin-page',
'dashicons-admin-plugins',
'dashicons-admin-post',
'dashicons-admin-settings',
'dashicons-admin-site',
'dashicons-admin-site-alt',
'dashicons-admin-site-alt2',
'dashicons-admin-site-alt3',
'dashicons-admin-tools',
'dashicons-admin-users',
// Welcome Screen.
'dashicons-welcome-add-page',
'dashicons-welcome-comments',
'dashicons-welcome-learn-more',
'dashicons-welcome-view-site',
'dashicons-welcome-widgets-menus',
'dashicons-welcome-write-blog',
// Post Formats.
'dashicons-format-aside',
'dashicons-format-audio',
'dashicons-format-chat',
'dashicons-format-gallery',
'dashicons-format-image',
'dashicons-format-quote',
'dashicons-format-status',
'dashicons-format-video',
// Media.
'dashicons-media-archive',
'dashicons-media-audio',
'dashicons-media-code',
'dashicons-media-default',
'dashicons-media-document',
'dashicons-media-interactive',
'dashicons-media-spreadsheet',
'dashicons-media-text',
'dashicons-media-video',
'dashicons-playlist-audio',
'dashicons-playlist-video',
'dashicons-controls-back',
'dashicons-controls-forward',
'dashicons-controls-pause',
'dashicons-controls-play',
'dashicons-controls-repeat',
'dashicons-controls-skipback',
'dashicons-controls-skipforward',
'dashicons-controls-volumeoff',
'dashicons-controls-volumeon',
// Image Editing.
'dashicons-image-crop',
'dashicons-image-filter',
'dashicons-image-flip-horizontal',
'dashicons-image-flip-vertical',
'dashicons-image-rotate',
'dashicons-image-rotate-left',
'dashicons-image-rotate-right',
// Taxonomies.
'dashicons-block-default',
'dashicons-button',
'dashicons-category',
'dashicons-tag',
// Alignment.
'dashicons-align-center',
'dashicons-align-full-width',
'dashicons-align-left',
'dashicons-align-none',
'dashicons-align-pull-left',
'dashicons-align-pull-right',
'dashicons-align-right',
'dashicons-align-wide',
// Editor.
'dashicons-editor-aligncenter',
'dashicons-editor-alignleft',
'dashicons-editor-alignright',
'dashicons-editor-bold',
'dashicons-editor-break',
'dashicons-editor-code',
'dashicons-editor-contract',
'dashicons-editor-customchar',
'dashicons-editor-expand',
'dashicons-editor-help',
'dashicons-editor-indent',
'dashicons-editor-insertmore',
'dashicons-editor-italic',
'dashicons-editor-justify',
'dashicons-editor-kitchensink',
'dashicons-editor-ltr',
'dashicons-editor-ol',
'dashicons-editor-outdent',
'dashicons-editor-paragraph',
'dashicons-editor-paste-text',
'dashicons-editor-paste-word',
'dashicons-editor-quote',
'dashicons-editor-removeformatting',
'dashicons-editor-rtl',
'dashicons-editor-spellcheck',
'dashicons-editor-strikethrough',
'dashicons-editor-table',
'dashicons-editor-textcolor',
'dashicons-editor-ul',
'dashicons-editor-underline',
'dashicons-editor-unlink',
'dashicons-editor-video',
// WordPress.
'dashicons-ellipsis',
'dashicons-wordpress',
'dashicons-wordpress-alt',
// Miscellaneous.
'dashicons-airplane',
'dashicons-album',
'dashicons-analytics',
'dashicons-archive',
'dashicons-art',
'dashicons-awards',
'dashicons-backup',
'dashicons-bank',
'dashicons-beer',
'dashicons-bell',
'dashicons-book',
'dashicons-book-alt',
'dashicons-buddicons-activity',
'dashicons-buddicons-bbpress-logo',
'dashicons-buddicons-buddypress-logo',
'dashicons-buddicons-community',
'dashicons-buddicons-forums',
'dashicons-buddicons-friends',
'dashicons-buddicons-groups',
'dashicons-buddicons-pm',
'dashicons-buddicons-replies',
'dashicons-buddicons-topics',
'dashicons-buddicons-tracking',
'dashicons-building',
'dashicons-businessman',
'dashicons-businessperson',
'dashicons-businesswoman',
'dashicons-calculator',
'dashicons-calendar',
'dashicons-calendar-alt',
'dashicons-camera',
'dashicons-camera-alt',
'dashicons-car',
'dashicons-carrot',
'dashicons-cart',
'dashicons-chart-area',
'dashicons-chart-bar',
'dashicons-chart-line',
'dashicons-chart-pie',
'dashicons-clipboard',
'dashicons-clock',
'dashicons-cloud',
'dashicons-cloud-saved',
'dashicons-cloud-upload',
'dashicons-coffee',
'dashicons-color-picker',
'dashicons-columns',
'dashicons-cover-image',
'dashicons-dashboard',
'dashicons-desktop',
'dashicons-dismiss',
'dashicons-download',
'dashicons-drumstick',
'dashicons-edit',
'dashicons-edit-large',
'dashicons-edit-page',
'dashicons-email',
'dashicons-email-alt',
'dashicons-email-alt2',
'dashicons-embed-audio',
'dashicons-embed-generic',
'dashicons-embed-photo',
'dashicons-embed-post',
'dashicons-embed-video',
'dashicons-excerpt-view',
'dashicons-exit',
'dashicons-external',
'dashicons-facebook',
'dashicons-facebook-alt',
'dashicons-feedback',
'dashicons-filter',
'dashicons-flag',
'dashicons-food',
'dashicons-forms',
'dashicons-fullscreen-alt',
'dashicons-fullscreen-exit-alt',
'dashicons-games',
'dashicons-google',
'dashicons-grid-view',
'dashicons-groups',
'dashicons-hammer',
'dashicons-heart',
'dashicons-hidden',
'dashicons-html',
'dashicons-id',
'dashicons-id-alt',
'dashicons-index-card',
'dashicons-info',
'dashicons-info-outline',
'dashicons-insert',
'dashicons-insert-after',
'dashicons-insert-before',
'dashicons-instagram',
'dashicons-laptop',
'dashicons-layout',
'dashicons-leftright',
'dashicons-lightbulb',
'dashicons-list-view',
'dashicons-location',
'dashicons-location-alt',
'dashicons-lock',
'dashicons-marker',
'dashicons-megaphone',
'dashicons-menu',
'dashicons-menu-alt',
'dashicons-menu-alt2',
'dashicons-menu-alt3',
'dashicons-microphone',
'dashicons-migrate',
'dashicons-minus',
'dashicons-money',
'dashicons-money-alt',
'dashicons-move',
'dashicons-nametag',
'dashicons-networking',
'dashicons-no',
'dashicons-no-alt',
'dashicons-open-folder',
'dashicons-palmtree',
'dashicons-paperclip',
'dashicons-pdf',
'dashicons-performance',
'dashicons-pets',
'dashicons-phone',
'dashicons-pinterest',
'dashicons-plugins-checked',
'dashicons-plus',
'dashicons-plus-alt',
'dashicons-plus-alt2',
'dashicons-portfolio',
'dashicons-post-status',
'dashicons-pressthis',
'dashicons-printer',
'dashicons-privacy',
'dashicons-products',
'dashicons-randomize',
'dashicons-redo',
'dashicons-rest-api',
'dashicons-rss',
'dashicons-saved',
'dashicons-schedule',
'dashicons-screenoptions',
'dashicons-search',
'dashicons-share',
'dashicons-share-alt',
'dashicons-share-alt2',
'dashicons-shield',
'dashicons-shield-alt',
'dashicons-shortcode',
'dashicons-slides',
'dashicons-smartphone',
'dashicons-smiley',
'dashicons-sort',
'dashicons-sos',
'dashicons-star-empty',
'dashicons-star-filled',
'dashicons-star-half',
'dashicons-sticky',
'dashicons-store',
'dashicons-superhero',
'dashicons-superhero-alt',
'dashicons-table-col-after',
'dashicons-table-col-before',
'dashicons-table-col-delete',
'dashicons-table-row-after',
'dashicons-table-row-before',
'dashicons-table-row-delete',
'dashicons-tablet',
'dashicons-testimonial',
'dashicons-text',
'dashicons-text-page',
'dashicons-thumbs-down',
'dashicons-thumbs-up',
'dashicons-tickets',
'dashicons-tickets-alt',
'dashicons-tide',
'dashicons-translation',
'dashicons-trash',
'dashicons-twitter',
'dashicons-twitter-alt',
'dashicons-undo',
'dashicons-universal-access',
'dashicons-universal-access-alt',
'dashicons-unlock',
'dashicons-update',
'dashicons-update-alt',
'dashicons-upload',
'dashicons-vault',
'dashicons-video-alt',
'dashicons-video-alt2',
'dashicons-video-alt3',
'dashicons-visibility',
'dashicons-warning',
'dashicons-whatsapp',
'dashicons-xing',
'dashicons-yes',
'dashicons-yes-alt',
'dashicons-youtube',
);
}
/**
* Render Google Map field
*
* @param array $field Field configuration.
* @param mixed $value Field value.
* @param bool $required Whether field is required.
* @return void
*/
private function render_google_map_field( $field, $value, $required ) {
// Parse value - can be array with address, lat, lng.
$address = '';
$lat = '';
$lng = '';
if ( is_array( $value ) ) {
$address = isset( $value['address'] ) ? $value['address'] : '';
$lat = isset( $value['lat'] ) ? $value['lat'] : '';
$lng = isset( $value['lng'] ) ? $value['lng'] : '';
} elseif ( is_string( $value ) ) {
$address = $value;
}
echo '
';
// Address input.
echo '
';
echo '' . esc_html__( 'Address', 'airy-frontend-forms' ) . ' ';
echo ' ';
echo '
';
// Hidden inputs for coordinates.
echo '
';
echo '
';
// Optional: Display coordinates if available.
if ( ! empty( $lat ) && ! empty( $lng ) ) {
echo '
';
/* translators: %1$s: latitude, %2$s: longitude */
printf( esc_html__( 'Coordinates: %1$s, %2$s', 'airy-frontend-forms' ), esc_html( $lat ), esc_html( $lng ) );
echo '
';
}
echo '
';
}
/**
* Render Clone field
*
* @param array $field Field configuration.
* @param mixed $value Field value.
* @return void
*/
private function render_clone_field( $field, $value ) {
// Clone fields copy the structure of other fields.
// The sub_fields should contain the cloned fields.
$sub_fields = isset( $field['sub_fields'] ) ? $field['sub_fields'] : array();
// If sub_fields are empty, try to get them from ACF.
if ( empty( $sub_fields ) && function_exists( 'acf_get_field' ) && ! empty( $field['key'] ) ) {
$full_field = acf_get_field( $field['key'] );
if ( ! empty( $full_field['sub_fields'] ) ) {
$sub_fields = $full_field['sub_fields'];
}
}
if ( empty( $sub_fields ) ) {
echo '
' . esc_html__( 'No cloned fields available.', 'airy-frontend-forms' ) . '
';
return;
}
$clone_value = is_array( $value ) ? $value : array();
echo '
';
foreach ( $sub_fields as $sub_field ) {
$sub_field_key = isset( $sub_field['key'] ) ? $sub_field['key'] : $sub_field['name'];
$sub_value = isset( $clone_value[ $sub_field['name'] ] ) ? $clone_value[ $sub_field['name'] ] : '';
$sub_required = ! empty( $sub_field['required'] );
echo '
';
// Label.
$label = isset( $sub_field['label'] ) ? $sub_field['label'] : $sub_field['name'];
echo '' . esc_html( $label );
if ( $sub_required ) {
echo ' * ';
}
echo ' ';
// Render sub-field.
$this->render_sub_field( $sub_field, $field['key'] . '[' . $sub_field['name'] . ']', $sub_value, $sub_required );
echo '
';
}
echo '
';
}
/**
* Render taxonomies
*
* @return void
*/
private function render_taxonomies() {
if ( empty( $this->form->taxonomies ) ) {
return;
}
foreach ( $this->form->taxonomies as $taxonomy_config ) {
// v1.8.8 working logic: Skip if enabled is empty/false/missing.
if ( empty( $taxonomy_config['enabled'] ) ) {
continue;
}
$this->render_taxonomy( $taxonomy_config );
}
}
/**
* Render a single taxonomy
*
* @param array $taxonomy_config Taxonomy configuration.
* @return void
*/
private function render_taxonomy( $taxonomy_config ) {
$taxonomy = $taxonomy_config['taxonomy'];
$label = ! empty( $taxonomy_config['label'] ) ? $taxonomy_config['label'] : $taxonomy;
$display_type = ! empty( $taxonomy_config['display_type'] ) ? $taxonomy_config['display_type'] : 'checkbox';
$required = ! empty( $taxonomy_config['required'] );
$help_text = ! empty( $taxonomy_config['help_text'] ) ? $taxonomy_config['help_text'] : '';
// Get terms.
$terms = get_terms(
array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
)
);
if ( is_wp_error( $terms ) ) {
return; // Skip if taxonomy doesn't exist.
}
// Get selected terms if editing.
$selected_terms = array();
if ( $this->is_edit ) {
$selected_terms = wp_get_post_terms( $this->post_id, $taxonomy, array( 'fields' => 'ids' ) );
}
echo '
';
// Label.
echo '
';
echo esc_html( $label );
if ( $required ) {
echo ' * ';
}
echo ' ';
// If no terms, show helpful message.
if ( empty( $terms ) ) {
echo '
';
echo '
';
return;
}
// Render based on display type.
switch ( $display_type ) {
case 'checkbox':
$this->render_taxonomy_checkboxes( $taxonomy, $terms, $selected_terms );
break;
case 'radio':
$this->render_taxonomy_radios( $taxonomy, $terms, $selected_terms, $required );
break;
case 'dropdown':
default:
$this->render_taxonomy_dropdown( $taxonomy, $terms, $selected_terms, $required );
break;
}
// Help text.
if ( $help_text ) {
echo '
' . esc_html( $help_text ) . '
';
}
echo '
';
}
/**
* Render taxonomy as checkboxes
*
* @param string $taxonomy Taxonomy name.
* @param array $terms Array of term objects.
* @param array $selected_terms Array of selected term IDs.
* @return void
*/
private function render_taxonomy_checkboxes( $taxonomy, $terms, $selected_terms ) {
echo '
';
foreach ( $terms as $term ) {
$checked = in_array( $term->term_id, $selected_terms, true );
echo '';
echo ' ';
echo ' ' . esc_html( $term->name );
echo ' ';
}
echo '
';
}
/**
* Render taxonomy as radio buttons
*
* @param string $taxonomy Taxonomy name.
* @param array $terms Array of term objects.
* @param array $selected_terms Array of selected term IDs.
* @param bool $required Whether field is required.
* @return void
*/
private function render_taxonomy_radios( $taxonomy, $terms, $selected_terms, $required ) {
$selected = ! empty( $selected_terms ) ? $selected_terms[0] : '';
echo '
';
foreach ( $terms as $term ) {
$checked = ( (int) $selected === (int) $term->term_id );
echo '';
echo ' ';
echo ' ' . esc_html( $term->name );
echo ' ';
}
echo '
';
}
/**
* Render taxonomy as dropdown
*
* @param string $taxonomy Taxonomy name.
* @param array $terms Array of term objects.
* @param array $selected_terms Array of selected term IDs.
* @param bool $required Whether field is required.
* @return void
*/
private function render_taxonomy_dropdown( $taxonomy, $terms, $selected_terms, $required ) {
echo '
';
}
/**
* Render submit button
*
* @return void
*/
private function render_submit_button() {
$button_text = $this->is_edit
? __( 'Update', 'airy-frontend-forms' )
: __( 'Submit', 'airy-frontend-forms' );
echo '
';
}
/**
* Display success/error messages
*
* @return void
*/
private function display_messages() {
$user_id = get_current_user_id();
// Check for success message.
$success_message = get_transient( 'affcf_submission_success_' . $user_id );
if ( $success_message ) {
echo '
';
delete_transient( 'affcf_submission_success_' . $user_id );
delete_transient( 'affcf_submission_post_id_' . $user_id );
}
// Check for error message.
$error_message = get_transient( 'affcf_submission_error_' . $user_id );
if ( $error_message ) {
echo '
';
delete_transient( 'affcf_submission_error_' . $user_id );
}
}
}