Jake Vanderwerf
2025-11-04 42fa8304ddb811b0f725f245130f70c0f5e86a6c
inc/managers/CRUDManager.php
@@ -2,7 +2,9 @@
namespace JVBase\managers;
use JVBase\managers\UserTermsManager;
use JVBase\meta\MetaForm;
use JVBase\meta\MetaManager;
use JVBase\utility\Features;
use WP_User;
if (!defined('ABSPATH')) {
@@ -19,6 +21,7 @@
   protected array $filters;
   protected array $bulkActions;
   protected MetaManager $meta;
   protected MetaForm $form;
   protected array $taxonomies;
   protected array $statuses;
   protected array $fields;
@@ -47,6 +50,7 @@
      ];
      $this->init();
      add_filter('jvbAdditionalActions', [$this, 'createItem']);
   }
   protected function init():void
@@ -56,7 +60,7 @@
      $this->initTaxonomies();
      $this->initFilters();
      $this->meta = new MetaManager(null, 'post', $this->content);
      $this->form = new MetaForm();
      $plural = strtolower($this->config['plural']??$this->content.'s');
      $this->userCanPublish = (jvbUserIsVerified()) ?
               user_can($this->user_id, "publish_{$plural}") : false;
@@ -192,13 +196,13 @@
         'multiple'     => true,
         'destination'  => 'post'
      ];
      if (!jvbCheck('single_image', $this->config)) {
      if (!array_key_exists('single_image', $this->config) || $this->config['single_image'] === false) {
         $uploadConfig['destination'] = 'post_group';
      }
      $uploadConfig['destination'] = 'post_group';
      if (!jvbCheck('single_image', $this->config)) {
         $uploadConfig['group_title'] = 'Create '.$this->config['plural'];
         $uploadConfig['group_description'] = '<p>Drag images into groups. Each group becomes its own '.$this->singular.'.</p>
         $uploadConfig['label'] = 'Create '.$this->config['plural'];
         $uploadConfig['upload_text'] = '<p>Drag images into groups. Each group becomes its own '.$this->singular.'.</p>
                  <p>You can also select multiple images and click the "Add to Group" button.</p>
                  <p>If a '.$this->singular.' has multiple images, you can select the '.jvbIcon('star').' to set an image as the main one.</p>
                  <p>Images left ungrouped will become individual '.$this->plural.'</p>
@@ -207,7 +211,6 @@
         $uploadConfig['description'] = 'Each image will become its own '.$this->singular.'.';
      }
      ?>
      <button type="button" class="create-item row" title="Create New <?= $this->singular?>"><?=jvbIcon('add') ?><span class="screen-reader-text">Create New <?= $this->singular?></span></button>
      <details open class="uploader">
         <summary class="row btw"><?= $this->config['upload_title'] ?? 'Bulk Upload '.$this->plural?></summary>
         <?php
@@ -256,7 +259,7 @@
   protected function renderFilters():void
   {
      ?>
      <div class="all-filters col start">
      <div class="all-filters col start" data-ignore>
         <div class="search row start nowrap">
            <span class="label">Search:</span>
            <?= jvbSearch() ?>
@@ -585,7 +588,7 @@
   protected function renderModals():void
   {
      $this->renderCreateModal();
//    $this->renderCreateModal();
      $this->renderEditModal();
      $this->renderBulkEditModal();
   }
@@ -603,6 +606,7 @@
      ob_start();
      ?>
      <form class="edit-form" data-save="content" data-form-id="edit-<?=$this->content?>">
         <?= jvbFormStatus() ?>
         <input type="hidden" name="form-id" value="<?=uniqid('new-')?>" />
         <input type="hidden" name="content" value="<?=$this->content?>" />
         <div class="fields">
@@ -632,9 +636,14 @@
            } else {
               $tabs = false;
            }
            $fields = $this->fields;
            $isTimeline = Features::forContent($this->content)->has('is_timeline');
            $fields = $this->fields;
            if (!$isTimeline) {
            $first = ['post_thumbnail', 'post_title', 'price'];
            foreach ($first as $f) {
               if (array_key_exists($f, $fields)) {
                  if ($tabs) {
@@ -646,6 +655,40 @@
                  unset($fields[$f]);
               }
            }
            }
            if ($isTimeline) {
               $temp = array_filter($fields, function ($field) {
                  if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
                     return true;
                  }
                  return false;
               });
               $config = [
                  'type'      => 'gallery',
                  'subtype'   => 'timeline',
                  'data'      => 'timeline',
                  'label'     => 'Progression',
                  'fields' => $temp
               ];
               $content = '';
               foreach ($fields as $slug=> $field) {
                  if (!array_key_exists('for_all', $field) || $field['for_all'] === false) {
                     $content .= $this->form->render($slug, null, $field, false, true);
                  }
               }
               $content .= $this->meta->render('form', 'timeline', $config, false,true);
               $tabs['progression']['content'] = $content;
               $this->fields = array_filter($fields, function ($field) {
                  if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
                     return false;
                  }
                  return true;
               });
               $fields = $this->fields;
            }
            foreach ($fields as $n => $config) {
               if ($tabs) {
                  $section = (array_key_exists('section', $config)) ? $config['section'] : 'basic';
@@ -653,10 +696,8 @@
               } else {
                  $this->meta->render('form', $n, $config);
               }
            }
            if ($tabs) {
               jvbRenderTabs($tabs);
            }
@@ -667,6 +708,58 @@
      return ob_get_clean();
   }
   protected function renderTimelineFields():string
   {
      ob_start();
      ?>
      <div class="repeater-field timeline-repeater" data-timeline data-field="fields">
         <div class="repeater-rows" data-repeater-container>
            <!-- Parent row (non-draggable) -->
            <div class="repeater-row parent-row" data-row-index="0" data-id="">
               <div class="row-header">
                  <h4>Before (Starting Point)</h4>
               </div>
               <div class="row-fields">
                  <?php $this->renderRowFields(); ?>
               </div>
            </div>
            <!-- Child rows will be added dynamically -->
         </div>
         <button type="button" class="add-repeater-row btn secondary">
            <?= jvbIcon('add') ?>
            <span>Add Progress Step</span>
         </button>
      </div>
      <?php
      return ob_get_clean();
   }
   protected function renderRowFields():void
   {
      $fields = $this->fields;
      // Render priority fields first
      $first = ['post_thumbnail', 'post_title', 'price'];
      foreach ($first as $f) {
         if (array_key_exists($f, $fields)) {
            $this->meta->render('form', $f, $fields[$f]);
            unset($fields[$f]);
         }
      }
      // Render remaining fields
      foreach ($fields as $name => $config) {
         if (!array_key_exists('hidden', $config) || !$config['hidden']) {
            $this->meta->render('form', $name, $config);
         }
      }
   }
   protected function getApplicableStatuses(string $prefix) {
      foreach ($this->statuses as $status => $config) {
         if ($status === 'all') {
@@ -713,6 +806,7 @@
      ob_start();
      ?>
      <form class="bulk-edit-form" data-save="content" data-form-id="bulk-edit-<?=$this->content?>">
         <?= jvbFormStatus() ?>
         <div class="selected"></div>
         <p class="description">You can unselect items by clicking the image here.</p>
         <p class="hint"><strong>IMPORTANT: </strong> Whatever changes you make here will be applied to all selected <?=$this->plural?>.</p>
@@ -776,6 +870,18 @@
      $this->renderGridView();
      $this->renderTableView();
      $this->renderTableRow();
      if (Features::forContent($this->content)->has('is_timeline')) {
         $temp = array_filter($this->fields, function ($field) {
            if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
               return true;
            }
            return false;
         });
         $form = new MetaForm();
         echo '<template class="uploadTimeline">';
         $form->renderImagePreview(null,$temp);
         echo '</template>';
      }
      echo jvbGetEmptyStateTemplate();
      echo jvbGetGalleryPreviewTemplate();
@@ -886,7 +992,7 @@
              data-save="content"
              data-content="<?= esc_attr($this->content) ?>"
              data-form-id="content-table-<?= esc_attr($this->content) ?>">
            <?= jvbFormStatus() ?>
            <?= $this->renderTableActions() ?>
            <table>
@@ -1024,4 +1130,17 @@
      <?php
      return ob_get_clean();
   }
   public function createItem(array $actions):array
   {
      ob_start();
      $this->renderCreateModal();
      $content = ob_get_clean();
      $create = [
         'button' => '<button type="button" class="create-item row" title="Create New '.$this->singular.'">'.jvbIcon('add').'<span class="screen-reader-text">Create New '.$this->singular.'</span></button>',
         'content'   => $content,
      ];
      $actions[] = $create;
      return $actions;
   }
}