handleConditionalField($config) : ''; if (!array_key_exists('type', $config)) { return $out; } if (array_key_exists('display', $config) && $config['display'] === 'hidden'){ $out = ''; if (!$return) { echo $out; } return $out; } ob_start(); $type = array_map( 'ucfirst', explode('_', $config['type'])); $type = implode('', $type); $method = 'render' . $type . 'Field'; $nameTemp = implode('', array_map('ucfirst', explode('_', $name))); $nameMethod = 'render'.$nameTemp.'Field'; if(function_exists($nameMethod)) { call_user_func($nameMethod, $value, $config); } elseif (function_exists($method)) { call_user_func($method, $value, $config); } elseif (method_exists($this, $method)) { $this->$method($name, $value, $config); } $out = ob_get_clean(); do_action('jvbRenderFormField', $name, $config, $value); $out = apply_filters('jvbFilterRenderFormField', $out, $name, $config, $value); if (!$return) { echo $out; } return $out; } public function renderTextField(string $name, mixed $value, array $field):void { // Use field-specific value if provided, otherwise use the meta value $display_value = isset($field['value']) ? $field['value'] : $value; $conditional = $this->handleConditionalField($field); $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $placeholder = (array_key_exists('placeholder', $field)) ? ' placeholder="'.$field['placeholder'].'"' : ''; $autocomplete = (array_key_exists('autocomplete', $field)) ? ' autocomplete="'.$field['autocomplete'].'"' : ''; ?>
data-field=""> > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
outputCharacterCountJS(); } } private function renderTelField(string $name, mixed $value, array $field):void {$describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Use field-specific value if provided, otherwise use the meta value $display_value = isset($field['value']) ? $field['value'] : $value; $conditional = $this->handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $placeholder = (array_key_exists('placeholder', $field)) ? ' placeholder="'.$field['placeholder'].'"' : ''; $autocomplete = (array_key_exists('autocomplete', $field)) ? ' autocomplete="'.$field['autocomplete'].'"' : ''; ?>
data-field=""> > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
outputCharacterCountJS(); } } private function renderEmailField(string $name, mixed $value, array $field):void { $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Use field-specific value if provided, otherwise use the meta value $display_value = isset($field['value']) ? $field['value'] : $value; $conditional = $this->handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $placeholder = (array_key_exists('placeholder', $field)) ? ' placeholder="'.$field['placeholder'].'"' : ''; $autocomplete = (array_key_exists('autocomplete', $field)) ? ' autocomplete="'.$field['autocomplete'].'"' : ''; ?>
data-field=""> " name="" value="" > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
outputCharacterCountJS(); } } private function renderUrlField(string $name, mixed $value, array $field):void { $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Use field-specific value if provided, otherwise use the meta value $display_value = isset($field['value']) ? $field['value'] : $value; $conditional = $this->handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $placeholder = (array_key_exists('placeholder', $field)) ? ' placeholder="'.$field['placeholder'].'"' : ''; $autocomplete = (array_key_exists('autocomplete', $field)) ? ' autocomplete="'.$field['autocomplete'].'"' : ''; ?>
data-field=""> > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
outputCharacterCountJS(); } } private function renderNumberField(string $name, mixed $value, array $field):void { $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; $description = ''; $description .= $field['description']??''; $conditional = $this->handleConditionalField($field); $min = isset($field['min']) ? (float)$field['min'] : 0; $max = isset($field['max']) ? (float)$field['max'] : 100; $step = isset($field['step']) ? (float)$field['step'] : 1; $data = ''; if (array_key_exists('data', $field) && !empty($field['data'])) { foreach($field['data'] as $key => $v) { if ($v === '') { $data .= ' data-'.$key; } else { $data .= ' data-'.$key.'="'.$v.'"'; } } } if (empty($value)) { $value = $field['default']??0; } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $autocomplete = (array_key_exists('autocomplete', $field)) ? ' autocomplete="'.$field['autocomplete'].'"' : ''; ?>
data-field="">
> >
renderDescription($description, $name); ?>
handleConditionalField($field); $quill = (array_key_exists('quill', $field) && $field['quill'] == true) ? ' data-editor="true"' : ''; if ($quill !== '') { $allowImages = array_key_exists('allowImage', $field); $quill .= ($allowImages) ? ' data-allowimage="true"' : ' data-allowimage="false"'; } // Handle array values if (is_array($value)) { $value = implode(', ', $value); } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $placeholder = (array_key_exists('placeholder', $field)) ? ' placeholder="'.$field['placeholder'].'"' : ''; ?>
data-field=""> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
renderCheckboxField($name, $value, $field); } private function renderCheckboxField(string $name, mixed $value, array $field):void { $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; $value = !is_array($value) ? explode(',', $value) : $value; $limit = isset($field['limit']) ? (int)$field['limit'] : 0; $conditional = $this->handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
data-field="">
$label) : ?> []" value="" >
renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
handleConditionalField($field); if (!array_key_exists('label', $field)) { error_log('No label for: '.print_r($name, true)); } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
data-field="">
$label) : ?> >
renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
handleConditionalField($field); $row_label = isset($field['row_label']) ? $field['row_label'] : ''; $rowTitle = (array_key_exists('new_row', $field)) ? $field['new_row'] : 'New Item'; if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; ?>
>

$row) { $this->renderRepeaterRow($field['fields'], $row, $index, $name, $rowTitle); } } ?>
renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
> # getRowTitle($fields, $values, $rowTitle)); ?>
$field) : if ($base_name === '') { $field_name = $slug; } else { $field_name = sprintf('%s:%s:%s', $base_name, $index, $slug); } $field_value = isset($values[$slug]) ? $values[$slug] : ''; $name = $field_name; $this->render($name, $field_value, $field); endforeach; ?>
$field) { if (in_array($field['type'], ['text', 'textarea']) && isset($values[$slug]) && !empty($values[$slug])) { return $values[$slug]; } } return $rowTitle; } private function renderTaxonomyField(string $name, mixed $value, array $field):void { $conditional = $this->handleConditionalField($field); $taxonomy = $field['taxonomy']; // Get currently selected terms $selected_terms = ($value === '') ? [] : explode(',', $value); // Convert selected term IDs to the format expected by single modal $processedSelected = []; if (!empty($selected_terms)) { foreach ($selected_terms as $termId) { if (is_numeric($termId)) { $term = get_term($termId, $taxonomy); if ($term && !is_wp_error($term)) { $processedSelected[$term->term_id] = [ 'name' => $term->name, 'path' => TaxonomySelector::getTermPath($term) ]; } } } } // Create configuration for single modal system $config = [ 'taxonomy' => $taxonomy, 'max' => $field['limit'] ?? 0, 'search' => $field['search'] ?? true, 'createNew' => $field['createNew'] ?? false, 'selected' => $processedSelected, 'base' => $field['base'] ?? '', ]; $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; ?>
data-field="">
'; echo $tax->render([], $extra); ?> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
handleConditionalField($field); // Process selected posts $selected_posts = $value; if (is_string($selected_posts)) { $selected_posts = !empty($selected_posts) ? explode(',', $selected_posts) : []; } elseif (!is_array($selected_posts)) { $selected_posts = []; } // Configure the post selector $config = [ 'multiple' => $field['multiple'] ?? true, 'maxSelections' => $field['limit'] ?? 0, 'search' => true, 'placeholder' => $field['placeholder'] ?? 'Search posts...', 'noResults' => 'No posts found', 'shop_id' => $field['shop_id'] ?? null, 'onClose' => 'updateMetaFormPost' ]; $postSelector = new PostSelector($field['post_type'], $config); $containerId = $name . '-post-selector'; $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; ?>
data-field="">
render($selected_posts, $containerId) ?> data-post-type="" value=""> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
handleConditionalField($field); // Ensure value is an array $values = is_array($value) ? $value : []; $original = $name; if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; $hidden = (array_key_exists('mode', $field) && $field['mode'] === 'hidden'); if (!$hidden) { ?>
data-field="">
"> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?> $config) { // Set the group context for proper field naming $config['group'] = $name; // Get the value for this specific field $field_value = $values[$field_name] ?? ''; // Handle conditional fields within the group if (isset($config['condition'])) { // Convert condition field reference to group context $condition_field = $config['condition']['field']; if (!str_contains($condition_field, '::')) { $config['condition']['field'] = $name . '::' . $condition_field; } } $this->render($field_name, $field_value, $config); } ?>
connect('maps'); if (!$googleMaps->isSetUp()) { echo '

Google Maps not configured. Please configure in Integrations settings.

'; return; } // Extract stored values if (is_string($value)) { $value = maybe_unserialize($value); } $stored_data = is_array($value) ? $value : []; $address = $stored_data['address'] ?? ''; $lat = $stored_data['lat'] ?? ''; $lng = $stored_data['lng'] ?? ''; // Generate unique field ID $field_id = esc_attr($name); $map_id = $field_id . '_map'; // Handle grouped fields if (array_key_exists('group', $field)) { $name = $field['group'] . '::' . $name; } $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Prepare configuration for JavaScript initialization $js_config = [ 'fieldId' => $field_id, 'initialCoords' => (!empty($lat) && !empty($lng)) ? [ 'lat' => (float)$lat, 'lng' => (float)$lng ] : null ]; // IMPORTANT: Properly escape the JSON for use in HTML attribute $json_config = htmlspecialchars(json_encode($js_config), ENT_QUOTES, 'UTF-8'); ?>
> Current location: '.esc_html($stored_data['street']).'

'; echo '

Search below to change:

'; } ?> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
>

Available Items

    Page 1

    Selected Items ( max)

      post_type, $post_types)) { $item_type = 'post'; $item_title = $post->post_title; $item_object = $post->post_type; } } // Check if it's a term if (empty($item_type) && in_array('term', $object_types)) { foreach ($taxonomies as $taxonomy) { $term = get_term($item_id, $taxonomy); if (!is_wp_error($term) && $term) { $item_type = 'term'; $item_title = $term->name; $item_object = $term->taxonomy; break; } } } // Only output if we found the item if (!empty($item_type) && !empty($item_title)) { ?>
    handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; ?>
    data-field=""> renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
    'image', // 'image', 'video', 'document', 'any' 'accepted_types' => null, // null = use subtype defaults, or array of specific MIME types //Upload Behaviour 'multiple' => false, // Single or multiple uploads 'limit' => 0, // Max number of uploads (0 = unlimited) 'mode' => 'direct', // 'direct' or 'selection' //Destination 'destination' => 'meta', // 'meta', 'post', 'post_group' //Processing Options 'max_size' => null, // Override default size limits 'convert' => 'webp', // Image conversion format 'quality' => 80, // Conversion quality 'create_thumbnails' => true, ]; $config = array_merge($defaultConfig, $field); // Validate destination config if (in_array($config['destination'], ['post', 'post_group']) && empty($config['content'])) { error_log("Upload field '{$name}' has destination '{$config['destination']}' but no content defined"); return; } // Get accepted types $acceptedTypes = $this->getAllowedTypes($config); // Build accept attribute for input $acceptExtensions = $this->getMimeExtensions($acceptedTypes); $acceptAttr = implode(',', $acceptExtensions); // Determine field attributes $subtype = $config['subtype'] ?? 'image'; $multiple = $config['multiple'] ?? false; $limit = $config['limit'] ?? 0; $mode = $config['mode'] ?? 'direct'; $destination = $config['destination']; // Get existing attachments $attachmentIds = $this->parseAttachmentIds($value); // Determine field type for UI $fieldType = $multiple ? 'gallery' : 'single'; // Build data attributes $dataAttrs = [ 'data-field' => $name, 'data-upload-field' => '', 'data-mode' => $mode, 'data-type' => $fieldType, 'data-subtype' => $subtype, 'data-destination' => $destination, ]; if (!empty($field['content'])) { $dataAttrs['data-content'] = $field['content']; } if ($limit > 0) { $dataAttrs['data-limit'] = $limit; } // Build data attributes $conditional = $this->handleConditionalField($field); $describedBy = !empty($field['description']) ? ' aria-describedby="' . esc_attr($name) . '-help"' : ''; if (!empty($field['group'])) { $name = $field['group'] . '::' . $name; } // Convert data attributes to string $dataAttrString = ''; foreach ($dataAttrs as $attr => $val) { $dataAttrString .= ' ' . $attr . ($val !== '' ? '="' . esc_attr($val) . '"' : ''); } ?>
    >
    >

    Click to upload or drag and drop
    getAcceptedTypesLabel($subtype, $acceptExtensions)) ?> (max. formatFileSize($this->getMaxFileSize($subtype))) ?>)

    You can group images to create separate .

    If a has multiple images, you can select the to set an image as the main one.

    >
    [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ], 'video' => [ 'video/mp4', 'video/webm', 'video/ogg', 'video/ogv', 'video/quicktime' ], 'document' => [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/plain', 'text/csv' ], 'any' => [] // Will be merged from all types ]; // If specific types are defined, use those if (!empty($config['accepted_types'])) { return is_array($config['accepted_types']) ? $config['accepted_types'] : [$config['accepted_types']]; } // Otherwise use subtype defaults $subtype = $config['subtype'] ?? 'image'; if ($subtype === 'any') { return array_merge( $typeMap['image'], $typeMap['video'], $typeMap['document'] ); } return $typeMap[$subtype] ?? $typeMap['image']; } /** * Parse attachment IDs from value */ private function parseAttachmentIds(mixed $value): array { if (empty($value)) return []; if (is_array($value)) { return array_filter(array_map('absint', $value)); } return array_filter(array_map('absint', explode(',', $value))); } /** * Get file extensions for MIME types */ private function getMimeExtensions(array $mimeTypes): array { $extensionMap = [ 'image/jpeg' => ['.jpg', '.jpeg'], 'image/png' => ['.png'], 'image/gif' => ['.gif'], 'image/webp' => ['.webp'], 'video/mp4' => ['.mp4'], 'video/webm' => ['.webm'], 'video/ogg' => ['.ogg'], 'video/ogv' => ['.ogv'], 'video/quicktime' => ['.mov'], 'application/pdf' => ['.pdf'], 'application/msword' => ['.doc'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['.docx'], 'application/vnd.ms-excel' => ['.xls'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['.xlsx'], 'text/plain' => ['.txt'], 'text/csv' => ['.csv'], ]; $extensions = []; foreach ($mimeTypes as $mime) { if (isset($extensionMap[$mime])) { $extensions = array_merge($extensions, $extensionMap[$mime]); } } return array_unique($extensions); } /** * Get max file size for subtype */ private function getMaxFileSize(string $subtype): int { $sizes = [ 'image' => 5242880, // 5MB 'video' => 104857600, // 100MB 'document' => 10485760 // 10MB ]; return $sizes[$subtype] ?? $sizes['image']; } /** * Get human-readable file size label */ private function getMaxFileSizeLabel(string $subtype): string { $bytes = $this->getMaxFileSize($subtype); $mb = round($bytes / 1048576); return "{$mb}MB"; } /** * Format file size for display */ private function formatFileSize(int $bytes): string { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 1) . 'GB'; } if ($bytes >= 1048576) { return number_format($bytes / 1048576, 1) . 'MB'; } if ($bytes >= 1024) { return number_format($bytes / 1024, 1) . 'KB'; } return $bytes . 'B'; } /** * Get accepted types label */ private function getAcceptedTypesLabel(string $subtype, array $extensions): string { $labels = [ 'image' => 'JPG, PNG, GIF, or WEBP', 'video' => 'MP4, WEBM, or MOV', 'document' => 'PDF, DOC, XLS, or TXT', 'any' => 'Images, Videos, or Documents' ]; return $labels[$subtype] ?? strtoupper(implode(', ', array_map(function($ext) { return ltrim($ext, '.'); }, array_slice($extensions, 0, 3)))); } /** * Render existing attachment */ private function renderExistingAttachment(int $attachmentId, string $subtype): string { $attachment = get_post($attachmentId); if (!$attachment) return ''; $url = wp_get_attachment_url($attachmentId); $thumbUrl = $subtype === 'image' ? wp_get_attachment_image_url($attachmentId, 'medium') : $url; ob_start(); ?>
    <?= esc_attr(get_post_meta($attachmentId, '_wp_attachment_image_alt', true)) ?>
    >
    >

    Click to upload or drag and drop
    JPG, PNG, GIF, or WEBP (max. 5MB)

    You can group images to create separate .

    If a has multiple images, you can select the to set an image as the main one.

    >
    handleConditionalField($field); if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } //TODO: This can probably just be a wrapper for renderImageField... ?>

    Click to upload or drag and drop
    JPG, PNG, GIF, or WEBP (max. 5MB)

    > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?> handleConditionalField($field); $default = isset($field['default']) ? $field['default'] : ''; $value = !empty($value) ? $value : $default; if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
    > renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
    $method_name(); } echo ($content == '') ? '' : sprintf( '
    %s
    ', esc_attr($name), $content ); } private function renderDateField(string $name, mixed $value, array $field):void { $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; $conditional = $this->handleConditionalField($field); $format = !empty($field['format']) ? $field['format'] : 'Y-m-d'; // Format the date if we have a value if (!empty($value)) { $date = DateTime::createFromFormat($format, $value); if ($date) { $value = $date->format('Y-m-d'); // HTML date input requires Y-m-d format } } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
    data-field="">
    > data-format="" >
    renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
    handleConditionalField($field); $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Convert various time formats to HTML time input format (HH:MM) if (!empty($value)) { // If it's already in HH:MM format, use as-is if (preg_match('/^\d{2}:\d{2}$/', $value)) { // Value is already in correct format } else { // Try to parse and convert $timestamp = strtotime($value); if ($timestamp !== false) { $value = date('H:i', $timestamp); } else { $value = ''; } } } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
    data-field="">
    > >
    renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
    handleConditionalField($field); $describedBy = (!empty($field['description'])) ? ' aria-describedby="'.$name.'-help"' : ''; // Convert datetime to HTML datetime-local format (YYYY-MM-DDTHH:MM) if (!empty($value)) { $date = DateTime::createFromFormat('Y-m-d H:i:s', $value); if (!$date) { // Try alternative formats $formats = ['Y-m-d\TH:i:s', 'Y-m-d\TH:i', 'Y-m-d H:i']; foreach ($formats as $format) { $date = DateTime::createFromFormat($format, $value); if ($date) break; } } if ($date) { $value = $date->format('Y-m-d\TH:i'); // HTML datetime-local format } else { $value = ''; } } if (array_key_exists('group', $field)) { $name = $field['group'].'::'.$name; } ?>
    data-field="">
    > >
    renderHint($field['hint']); } ?> renderDescription($field['description'], $name); } ?>
    '.jvbIcon('question').' '; echo $out; } protected function renderHint(array|string $hint):void { if (is_array($hint)) { $out = ''; foreach($hint as $h) { $out .= '

    '.$h.'

    '; } } else { $out = '

    '.$hint.'

    '; } echo $out; } }