| | |
| | | $validationAttrs = $this->buildValidationAttributes($field); |
| | | $conditional = array_key_exists('condition', $field) ? $this->handleConditionalField($field) : ''; |
| | | |
| | | $customData = ''; |
| | | if (array_key_exists('data', $field) && !empty($field['data'])) { |
| | | foreach ($field['data'] as $key => $v) { |
| | | $customData .= ($v === '') ? ' data-' . $key : ' data-' . $key . '="' . $v . '"'; |
| | | } |
| | | } |
| | | ?> |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <?php $this->renderLabel($name, $field); ?> |
| | |
| | | name="<?= esc_attr($data['name']) ?>" |
| | | value="<?= esc_attr($data['value']) ?>" |
| | | <?= $inputAttrs ?> |
| | | <?= $customData?> |
| | | > |
| | | <span class="validation-icon success" hidden aria-hidden="true"> |
| | | <?= jvbIcon('check-circle') ?> |
| | |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <?php $this->renderLabel($name, $field); ?> |
| | |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?> row" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <?php $this->renderLabel($name, $field); ?> |
| | |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <?php $this->renderLabel($name, $field); ?> |
| | |
| | | <select |
| | | id="<?= esc_attr($data['id']) ?>" |
| | | name="<?= esc_attr($data['name']) ?>" |
| | | <?= $inputAttrs ?> |
| | | > |
| | | <?= $inputAttrs ?>> |
| | | <?php foreach ($field['options'] as $key => $label) : ?> |
| | | <option value="<?= esc_attr($key) ?>" <?php selected($value, $key); ?>> |
| | | <?= esc_html($label) ?> |
| | |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <fieldset> |
| | |
| | | <div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <fieldset> |
| | |
| | | <div class="field true-false <?= esc_attr($name) ?> row btw" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <label class="toggle-switch row" <?= $describedBy ?>> |
| | |
| | | |
| | | private function renderRepeaterField(string $name, mixed $value, array $field):void |
| | | { |
| | | error_log('Rendering Repeater Field!'); |
| | | $values = is_array($value) ? $value : array(); |
| | | |
| | | $conditional = $this->handleConditionalField($field); |
| | |
| | | ?> |
| | | <div class="field repeater <?=$name?>" |
| | | data-field="<?= esc_attr($name); ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $describedBy ?> |
| | | <?= $row_label ? 'data-label="' . esc_attr($row_label) . '"' : ''; ?> |
| | | <?=$conditional?>> |
| | |
| | | ?> |
| | | </div> |
| | | |
| | | <template class="<?=uniqid('repeaterTemplate')?>"> |
| | | <template class="<?=uniqid('repeaterRow')?>"> |
| | | <?php $this->renderRepeaterRow($field['fields'], array(), '', '', $rowTitle); ?> |
| | | </template> |
| | | |
| | |
| | | protected function renderGroupField(string $name, mixed $value, array $field): void |
| | | { |
| | | if (!array_key_exists('fields', $field) || empty($field['fields'])) { |
| | | error_log('No fields to render'); |
| | | return; |
| | | } |
| | | |
| | | |
| | | $values = is_array($value) ? $value : []; |
| | | $original = $name; |
| | | |
| | |
| | | $conditional = $this->handleConditionalField($field); |
| | | $validationAttrs = $this->buildValidationAttributes($field); |
| | | $describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : ''; |
| | | |
| | | $fieldset = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'details' : 'fieldset'; |
| | | $legend = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'summary' : 'legend'; |
| | | ?> |
| | | <fieldset class="field group <?= esc_attr($name) ?>" |
| | | <<?= $fieldset?> class="field group <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?> |
| | | <?= $describedBy ?>> |
| | | <legend><?= esc_html($field['label']) ?></legend> |
| | | <<?=$legend?>><?= esc_html($field['label']) ?></<?=$legend?>> |
| | | |
| | | <?php $this->renderHintAndDescription($field, $name); ?> |
| | | |
| | |
| | | </div> |
| | | |
| | | <span class="validation-message" hidden role="alert"></span> |
| | | </fieldset> |
| | | </<?= $fieldset?>> |
| | | <?php |
| | | } |
| | | |
| | |
| | | //Processing Options |
| | | 'max_size' => null, // Override default size limits |
| | | 'convert' => 'webp', // Image conversion format |
| | | 'quality' => 80, // Conversion quality |
| | | 'quality' => 90, // Conversion quality |
| | | 'create_thumbnails' => true, |
| | | ]; |
| | | $config = array_merge($defaultConfig, $field); |
| | |
| | | } |
| | | ?> |
| | | <div class="field upload <?= esc_attr($name) ?>" |
| | | data-field="<?=esc_attr($name)?>" |
| | | data-field-type="upload" |
| | | <?= $dataAttrString ?> |
| | | <?= $conditional ?>> |
| | | |
| | |
| | | <?php endif; ?> |
| | | <div class="file-error"></div> |
| | | </div> |
| | | <?php jvbRenderProgressBar(); ?> |
| | | </div> |
| | | |
| | | |
| | |
| | | <div class="selection-controls"> |
| | | <div class="selected"> |
| | | <div class="field"> |
| | | <input type="checkbox" id="select-all-uploads" name="select-all-uploads"> |
| | | <input type="checkbox" id="select-all-uploads" data-select-all data-selects="item-grid" name="select-all-uploads"> |
| | | <label for="select-all-uploads"> |
| | | Select All |
| | | </label> |
| | |
| | | <?php |
| | | } |
| | | |
| | | private function renderExistingAttachment(int $attachmentId, string $subtype): string |
| | | { |
| | | ob_start(); |
| | | |
| | | switch ($subtype) { |
| | | case 'image': |
| | | $this->renderImagePreview($attachmentId); |
| | | break; |
| | | case 'video': |
| | | $this->renderVideoPreview($attachmentId); |
| | | break; |
| | | case 'document': |
| | | case 'file': |
| | | $this->renderFilePreview($attachmentId); |
| | | break; |
| | | default: |
| | | $this->renderImagePreview($attachmentId); |
| | | break; |
| | | } |
| | | |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | /** |
| | | * Get max file size for subtype |
| | | */ |
| | |
| | | $dataID = ($id) ? ['id' => $id] : ''; |
| | | ?> |
| | | <div class="item upload"<?= ($id) ? ' data-id="'.$id.'"' : '' ?>> |
| | | <div class="preview"> |
| | | <?php jvbRenderProgressBar('',true) ?> |
| | | <input type="checkbox" class="upload-select" name="select-item" id="select-item<?=$addID?>"> |
| | | <label for="select-item<?=$addID?>" aria-label="Select image"> |
| | | <?= ($attachment) ? $attachment : '<img> |
| | | <video></video> |
| | | <span></span>' ?> |
| | | </label> |
| | | <div class="item-actions row btw"> |
| | | <div class="radio-button"> |
| | | <input type="radio" class="featured btn" name="featured" id="featured" hidden> |
| | | <label for="featured"> |
| | | <?=jvbIcon('star')?> |
| | | <?=jvbIcon('star', ['style' => 'fill'])?> |
| | | <span class="screen-reader-text">Set as featured image</span> |
| | | </label> |
| | | </div> |
| | | |
| | | <button type="button" data-action="delete-upload" title="Remove from Group"> |
| | | <?=jvbIcon('trash')?> |
| | | </button> |
| | | <div class="preview"> |
| | | <?php jvbRenderProgressBar('',true) ?> |
| | | <input type="checkbox" class="upload-select" name="select-item" id="select-item<?=$addID?>"> |
| | | <label for="select-item<?=$addID?>" aria-label="Select image"> |
| | | <?= ($attachment) ?: '<img> |
| | | <video></video> |
| | | <span></span>' ?> |
| | | </label> |
| | | <div class="item-actions row btw"> |
| | | <div class="radio-button"> |
| | | <input type="radio" class="featured btn" name="featured" id="featured" hidden> |
| | | <label for="featured"> |
| | | <?=jvbIcon('star')?> |
| | | <?=jvbIcon('star', ['style' => 'fill'])?> |
| | | <span class="screen-reader-text">Set as featured image</span> |
| | | </label> |
| | | </div> |
| | | |
| | | <button type="button" data-action="delete-upload" title="Remove from Group"> |
| | | <?=jvbIcon('trash')?> |
| | | </button> |
| | | </div> |
| | | <details> |
| | | <summary class="row btw"><?=jvbIcon('pencil-simple')?><span>Edit Info</span></summary> |
| | | </div> |
| | | <details> |
| | | <summary class="row btw"><?=jvbIcon('pencil-simple')?><span>Edit Info</span></summary> |
| | | |
| | | <?php |
| | | <?php |
| | | $fields = array_key_exists('fields', $config) ? $config['fields'] : []; |
| | | |
| | | $fields = array_key_exists('fields', $config) ? $config['fields'] : []; |
| | | $fields = array_merge([ |
| | | 'upload_data' => [ |
| | | 'type' => 'group', |
| | | 'wrap' => 'details', |
| | | 'label' => 'Image Info', |
| | | 'hint' => 'These will be automatically generated if left blank.', |
| | | 'fields' => [ |
| | | 'image-title'.$addID => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Image Title', |
| | | 'value' => $title, |
| | | 'data' => $dataID |
| | | ], |
| | | 'image-alt-text'.$addID => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Alt Text', |
| | | 'value' => $alt, |
| | | 'hint' => 'Alt text helps the visually impaired, as well as some benefits for SEO.', |
| | | 'data' => $dataID |
| | | ], |
| | | 'image-caption'.$addID => [ |
| | | 'type' => 'textarea', |
| | | 'value' => $caption, |
| | | 'label' => 'Image Caption', |
| | | 'data' => $dataID |
| | | ] |
| | | ] |
| | | ] |
| | | ], $fields); |
| | | // Only add image_data if not already provided |
| | | if (!array_key_exists('image_data', $fields)) { |
| | | $fields['image_data'] = [ |
| | | 'type' => 'group', |
| | | 'wrap' => 'details', |
| | | 'label' => 'Image Info', |
| | | 'hint' => 'These will be automatically generated if left blank.', |
| | | 'fields' => [ |
| | | 'image-title'.$addID => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Image Title', |
| | | 'value' => $title, |
| | | 'data' => $dataID |
| | | ], |
| | | 'image-alt-text'.$addID => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Alt Text', |
| | | 'value' => $alt, |
| | | 'hint' => 'Alt text helps the visually impaired, as well as some benefits for SEO.', |
| | | 'data' => $dataID |
| | | ], |
| | | 'image-caption'.$addID => [ |
| | | 'type' => 'textarea', |
| | | 'value' => $caption, |
| | | 'label' => 'Image Caption', |
| | | 'data' => $dataID |
| | | ] |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | $meta = new MetaManager($id); |
| | | foreach ($fields as $field => $config) { |
| | | $meta->render('form', $field, $config); |
| | | } |
| | | ?> |
| | | </details> |
| | | $meta = new MetaManager($id); |
| | | foreach ($fields as $field => $config) { |
| | | $meta->render('form', $field, $config); |
| | | } |
| | | ?> |
| | | </details> |
| | | </div> |
| | | <?php |
| | | } |
| | |
| | | * Generic selector field renderer |
| | | * Handles both taxonomy and post selectors with consistent structure |
| | | */ |
| | | private function renderSelectorField(string $name, mixed $value, array $field, string $type): void |
| | | public function renderSelectorField(string $name, mixed $value, array $field, string $type): void |
| | | { |
| | | $conditional = $this->handleConditionalField($field); |
| | | $validationAttrs = $this->buildValidationAttributes($field); |
| | | $describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : ''; |
| | | |
| | | $isSimple = (array_key_exists('mode', $field) && $field['mode']==='simple'); |
| | | // Parse selected values |
| | | $value = (is_array($value)) ? array_filter(array_map('absint', $value)): $value; |
| | | $selected = ($value === '') ? [] : (is_array($value) ? $value : explode(',', $value)); |
| | |
| | | } |
| | | |
| | | ?> |
| | | <div class="field <?= esc_attr($type) ?> <?= esc_attr($name) ?>" |
| | | <div class="field selector <?= esc_attr($type) ?> <?= esc_attr($name) ?>" |
| | | <?= $conditional ?> |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="selector" |
| | | data-type="<?=esc_attr($field['type'])?>" |
| | | <?= $validationAttrs ?> |
| | | <?= $describedBy ?>> |
| | | |
| | |
| | | |
| | | <div class="field location <?= esc_attr($field_id) ?>" |
| | | data-field="<?= esc_attr($field_id) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | data-location-field-init="<?= $json_config ?>"<?=$describedBy?>> |
| | | |
| | | <?php |
| | |
| | | |
| | | return is_numeric($value) ? [$value] : []; |
| | | } |
| | | /** |
| | | * Render tag list field - inline tag input interface |
| | | */ |
| | | protected function renderTagListField(string $name, mixed $value, array $field): void |
| | | { |
| | | $values = is_array($value) ? $value : []; |
| | | $conditional = $this->handleConditionalField($field); |
| | | $validationAttrs = $this->buildValidationAttributes($field); |
| | | |
| | | if (array_key_exists('group', $field)) { |
| | | $name = $field['group'] . '::' . $name; |
| | | } |
| | | |
| | | $describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : ''; |
| | | |
| | | // Tag display format - defaults to first field value |
| | | $tagFormat = $field['tag_format'] ?? 'first_field'; |
| | | ?> |
| | | <div class="field tag-list <?= esc_attr($name) ?>" |
| | | data-field="<?= esc_attr($name) ?>" |
| | | data-field-type="<?=esc_attr($field['type'])?>" |
| | | data-tag-format="<?= esc_attr($tagFormat) ?>" |
| | | <?= $describedBy ?> |
| | | <?= $conditional ?> |
| | | <?= $validationAttrs ?>> |
| | | |
| | | <?php if (!empty($field['label'])): ?> |
| | | <h3><?= esc_html($field['label']) ?></h3> |
| | | <?php endif; ?> |
| | | |
| | | <!-- Inline input row --> |
| | | <div class="tag-input-row"> |
| | | <?php foreach ($field['fields'] as $subfield_name => $subfield_config): ?> |
| | | <?php |
| | | $subfield_config['label'] = $subfield_config['label'] ?? ucfirst($subfield_name); |
| | | $input_name = 'new_' . $subfield_name; |
| | | |
| | | // Store required state but don't render it on the input |
| | | // This prevents form submission validation but allows JS validation |
| | | |
| | | if (array_key_exists('required', $subfield_config)) { |
| | | $subfield_config['data']['required'] = true; |
| | | unset($subfield_config['required']); // Remove required for HTML rendering |
| | | } |
| | | $subfield_config['data']['ignore'] = true; |
| | | |
| | | $this->render($input_name, '', $subfield_config, false, false); |
| | | ?> |
| | | <?php endforeach; ?> |
| | | |
| | | <button type="button" class="button add-tag-item"> |
| | | <?= jvbIcon('plus') ?> <?= $field['add_label'] ?? 'Add' ?> |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- Tags display --> |
| | | <div class="tag-items"> |
| | | <?php foreach ($values as $index => $item_data): ?> |
| | | <?php $this->renderTagItem($field['fields'], $item_data, $index, $name, $tagFormat); ?> |
| | | <?php endforeach; ?> |
| | | </div> |
| | | |
| | | <!-- Template for new tags --> |
| | | <template class="<?=uniqid('tagListItem')?>"> |
| | | <?php $this->renderTagItem($field['fields'], [], '', $name, $tagFormat); ?> |
| | | </template> |
| | | |
| | | <?php if (!empty($field['hint'])): ?> |
| | | <?php $this->renderHint($field['hint']); ?> |
| | | <?php endif; ?> |
| | | |
| | | <?php if (!empty($field['description'])): ?> |
| | | <?php $this->renderDescription($field['description'], $name); ?> |
| | | <?php endif; ?> |
| | | </div> |
| | | <?php |
| | | } |
| | | |
| | | /** |
| | | * Render individual tag item |
| | | */ |
| | | protected function renderTagItem(array $fields, array $data, int|string $index, string $base_name, string $format): void |
| | | { |
| | | $tag_text = $this->getTagDisplayText($fields, $data, $format); |
| | | ?> |
| | | <div class="tag-item" data-index="<?= esc_attr($index) ?>"> |
| | | <span class="tag-label"><?= esc_html($tag_text) ?></span> |
| | | |
| | | <!-- Hidden inputs for data --> |
| | | <?php foreach ($fields as $field_name => $field_config): ?> |
| | | <?php |
| | | $value = $data[$field_name] ?? ''; |
| | | $full_name = is_string($index) ? $field_name : "{$base_name}:{$index}:{$field_name}"; |
| | | ?> |
| | | <input type="hidden" |
| | | name="<?= esc_attr($full_name) ?>" |
| | | value="<?= esc_attr($value) ?>" |
| | | data-field="<?= esc_attr($field_name) ?>" |
| | | data-field-type="<?=esc_attr($field_config['type'])?>" /> |
| | | <?php endforeach; ?> |
| | | |
| | | <button type="button" class="remove-tag" aria-label="Remove"> |
| | | <?= jvbIcon('x') ?> |
| | | </button> |
| | | </div> |
| | | <?php |
| | | } |
| | | |
| | | /** |
| | | * Get tag display text based on format |
| | | */ |
| | | protected function getTagDisplayText(array $fields, array $data, string $format): string |
| | | { |
| | | if (empty($data)) { |
| | | return 'New Item'; |
| | | } |
| | | |
| | | switch ($format) { |
| | | case 'first_field': |
| | | // Use the first field's value |
| | | $first_key = array_key_first($fields); |
| | | return $data[$first_key] ?? 'New Item'; |
| | | |
| | | case 'all_fields': |
| | | // Show all field values separated by commas |
| | | $values = array_filter(array_values($data)); |
| | | return implode(', ', $values) ?: 'New Item'; |
| | | |
| | | case 'custom': |
| | | // Custom format - would need callback |
| | | return 'New Item'; |
| | | |
| | | default: |
| | | // Format is a template string like "{name} ({email})" |
| | | if (strpos($format, '{') !== false) { |
| | | $text = $format; |
| | | foreach ($data as $key => $value) { |
| | | $text = str_replace('{' . $key . '}', $value, $text); |
| | | } |
| | | return $text; |
| | | } |
| | | // Use specific field name |
| | | return $data[$format] ?? 'New Item'; |
| | | } |
| | | } |
| | | } |