Jake Vanderwerf
2026-08-30 87237f15618e2320967b9380c360b89f7323d2e9
inc/meta/Form.php
@@ -2,6 +2,8 @@
namespace JVBase\meta;
use DateTime;
use JVBase\base\Site;
use JVBase\integrations\services\GoogleMaps;
use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
@@ -26,7 +28,10 @@
         $value = '';
      }
      if (!empty($config['hidden'])) {
         return '';
            if ($config['type'] !== 'upload') {
                return static::renderHidden($name, $value, $config);
            }
            return '';
      }
      $type = $config['type'] ?? 'text';
@@ -57,8 +62,15 @@
   {
      $id = $options['form-id'] ?? $endpoint;
      $classes = isset($options['classes']) ? ' class="' . implode(' ', $options['classes']) . '"' : '';
      $formID = isset($options['showFormID']) && $options['showFormID']!== false;
      $output = '<form id="' . esc_attr($endpoint) . '"' . $classes . ' data-save="' . esc_attr($endpoint) . '" data-form-id="' . esc_attr($id) . '">';
      $output = sprintf(
         '<form id="%s"%s data-save="%s"%s>',
         esc_attr($endpoint),
         $classes,
         esc_attr($endpoint),
         $formID ? ' data-form-id="'.esc_attr($id).'"' : '',
      );
      if (!empty($options['heading'])) {
         $output .= '<h2>' . esc_html($options['heading']) . '</h2>';
@@ -70,7 +82,9 @@
            $output .= '<p>' . esc_html($d) . '</p>';
         }
      }
      $allFields = $meta->getAll($fields);
      foreach ($allFields as $name => $value) {
         $config = $meta->config($name);
         $output .= static::render($name, $value, $config);
@@ -311,6 +325,15 @@
      return static::fieldWrap($name, $input, $config);
   }
    protected static function renderHidden(string $name, mixed $value, array $config):string
    {
        return sprintf(
            '<input type="hidden" name="%s" value="%s">',
            esc_attr($name),
            esc_attr($value)
        );
    }
   protected static function renderTextarea(string $name, mixed $value, array $config): string
   {
      $rows = $config['rows'] ?? 5;
@@ -351,13 +374,19 @@
      if (!array_key_exists('class', $config)) {
         $config['class']=[];
      }
      $config['class'][] ='quantity-input';
      $config['class'][] = 'quantity-input';
        $config['class'][] = 'row';
        $config['class'][] = 'nowrap';
        if (array_key_exists('isCart', $config) && $config['isCart'] === true) {
        }
      $attrs = static::inputAttrs($name, $config);
      $value = ($value !== '') ? ' value="'.esc_attr($value).'"' : '';
      $input = sprintf(
         '<div class="quantity">
         '<div class="row nowrap">
            <button type="button" class="decrease" title="%s" aria-label="Decrease %s">%s</button>
            <input type="number"%s%s />
            <button type="button" class="increase" title="%s" aria-label="Increase %s">%s</button>
@@ -557,17 +586,17 @@
         $checked = in_array($optValue, $values) ? ' checked' : '';
         $checkboxes .= sprintf(
            '
                    <input type="checkbox" name="%s[]" id="%s-%s" value="%s"%s />
                    <input type="checkbox" name="%s" id="%s-%s" value="%s"%s/>
            <label class="checkbox-option" for="%s-%s">
                    <span>%s</span>
                </label>',
            esc_attr($name),
            esc_attr($name),
            $optValue,
            esc_attr($optValue),
            esc_attr($optValue),
            $checked,
            esc_attr($name),
            $optValue,
            esc_attr($optValue),
            esc_html($optLabel)
         );
      }
@@ -821,15 +850,11 @@
         public static function renderExistingAttachment(int $attachmentId, string $subtype):string
         {
            switch ($subtype){
               case 'video':
                  return static::renderVideoPreview($attachmentId);
               case 'document':
               case 'file':
                  return static::renderFilePreview($attachmentId);
               default:
                  return static::renderImagePreview($attachmentId);
            }
            return match ($subtype) {
               'video' => static::renderVideoPreview($attachmentId),
               'document', 'file' => static::renderFilePreview($attachmentId),
               default => static::renderImagePreview($attachmentId),
            };
         }
         protected static function renderUploadItemStart(?int $attachmentId = null):string
         {
@@ -1005,7 +1030,7 @@
            }
            public static function renderImagePreview(?int $ID = null, ?array $additionalFields = null, bool $isTemplate = false):string
            {
               $out = static::renderUploadItemStart($ID, $isTemplate);
               $out = static::renderUploadItemStart($ID);
               //add image preview
               if ($ID) {
                  $out .= jvbFormatImage($ID, 'tiny', 'medium');
@@ -1353,8 +1378,15 @@
                  $plural
               );
               break;
            default:
               $attr = false;
               $single = false;
               $plural = false;
         }
         if (!$attr || !$single || !$plural) {
            return '';
         }
         $dataAttrs = [];
         if ($config['update']) {
            $dataAttrs[] = 'data-update="false"';
@@ -1421,7 +1453,10 @@
   protected static function renderLocation(string $name, mixed $value, array $config): string
   {
      $googleMaps = JVB()->connect('maps');
      if (!Site::hasIntegration('maps')) {
         return '';
      }
      $googleMaps = GoogleMaps::getInstance();
      if (!$googleMaps->isSetUp()) {
         return '<div class="notice notice-warning"><p>Google Maps not configured. Please configure in Integrations settings.</p></div>';
      }
@@ -1602,7 +1637,7 @@
                  $values = array_filter(array_values($values));
                  return implode(', ', $values) ?: 'New Item';
               default:
                  if (strpos($tagFormat, '{') !== false) {
                  if (str_contains($tagFormat, '{')) {
                     $text = $tagFormat;
                     foreach ($values as $key => $value) {
                        $text = str_replace('{'.$key.'}', $value, $text);
@@ -1627,16 +1662,21 @@
      );
      $input .= '<div class="repeater-items">';
      $rowTitle = array_key_exists('new_row', $config) ? $config['new_row'] : 'New Item';
        $hasTitle = array_key_exists('row_label', $config);
      if(!empty($rows)) {
         foreach ($rows as $index=>$row) {
            $input .= static::renderRepeaterRow($config['fields'], $row, $index, $name, $rowTitle);
                $titleReference = $hasTitle ? $config['row_label'] : null;
                $rowTitle = !is_null($titleReference) ? $row[$titleReference] : 'New Item';
            $input .= static::renderRepeaterRow($config['fields'], $row, $index, $name, $rowTitle, $titleReference);
         }
      }
      $input .= '</div>';
        $titleReference = $hasTitle ? $config['row_label'] : null;
      $input .= '<template class="'.uniqid('repeaterRow').'">';
         $input .= static::renderRepeaterRow($config['fields'], [], '','',$rowTitle);
         $input .= static::renderRepeaterRow($config['fields'], [], '','','New Item', $titleReference);
      $input .= '</template>';
      $input .= sprintf(
@@ -1648,27 +1688,25 @@
      unset($config['label']);
      return static::fieldWrap($name, $input, $config);
   }
      protected static function renderRepeaterRow(array $fields, array $values, int|string $index, string $name, string $rowTitle='New Item'):string
      protected static function renderRepeaterRow(array $fields, array $values, int|string $index, string $name, string $rowTitle='New Item', ?string $rowTitleField = null):string
      {
         $displayNumber = (is_string($index)) ? $index : ($index +1);
         $output = sprintf(
            '<div class="repeater-row" data-index="%s">
                  <button type="button" class="remove-row" title="Remove">
                     %s
                  </button>
            '<div class="repeater-row row nowrap" data-index="%s">
                        <span class="drag-handle row">%s</span>
                  <details%s>
                     <summary class="row x-btw repeater-row-header">
                        <span class="drag-handle">%s</span>
                        <span class="row-number">#%s</span>
                        <span class="row-title">%s</span>
                        <span class="row-title"%s>%s</span>
                     </summary>
                     <div class="repeater-row-content">',
            esc_attr($index),
            jvbIcon('trash'),
                jvbIcon('dots-six-vertical'),
            is_string($index) ? ' open' : '',
            jvbIcon('dots-six-vertical'),
            $displayNumber,
                is_null($rowTitleField) ? '' : 'data-label="'.$rowTitleField.'"',
            $rowTitle
         );
         foreach ($fields as $fieldName => $fieldConfig) {
@@ -1677,7 +1715,11 @@
            $output .= static::render($fullName, $fieldValue, $fieldConfig);
         }
         return $output.'</div></details></div>';
         return $output.sprintf('</div></details><button type="button" class="remove-row" title="Remove">
                     %s
                  </button></div>',
                    jvbIcon('trash'),
                );
      }
   /**
@@ -1706,7 +1748,7 @@
      foreach ($fields as $fieldName => $fieldConfig) {
         $fieldValue = $values[$fieldName] ?? '';
         $fullName = array_key_exists('wrap', $config) ? $fieldName : "{$name}:{$fieldName}";
         $fullName = "{$name}|{$fieldName}";
         $output .= static::render($fullName, $fieldValue, $fieldConfig);
      }