Jake Vanderwerf
2026-05-12 16cb63b05910055c31dca821c86f2eb815da99e3
inc/managers/queue/executors/UploadExecutor.php
@@ -2,10 +2,13 @@
namespace JVBase\managers\queue\executors;
use JVBase\managers\queue\{Executor, Operation, Progress, Result};
use JVBase\managers\Cache;
use JVBase\managers\UploadManager;
use JVBase\meta\Meta;
use Exception;
use JVBase\utility\Features;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use WP_Error;
if (!defined('ABSPATH')) {
   exit;
@@ -78,10 +81,12 @@
      $securedFiles = $data['secured_files'] ?? [];
      // Phase 1: File I/O — no DB, no transactions
      $prepared = [];
      foreach ($securedFiles as $securedFile) {
         $uploadIds[] = $securedFile['upload_id'];
         try {
            $result = $uploader->processUpload(
            $prepared[$securedFile['upload_id']] = $uploader->prepareFile(
               $securedFile['temp_path'],
               [
                  'file_type'     => $fileType,
@@ -93,35 +98,46 @@
                  'content'       => $data['content'] ?? '',
               ]
            );
            if (is_wp_error($result)) {
               $progress->failItem($securedFile['upload_id'], $result->get_error_message());
               $errors[] = $result->get_error_message();
               continue;
            }
            $processedResults[$securedFile['upload_id']] = [
               'upload_id' => $securedFile['upload_id'],
               'attachment_id' => $result['attachment_id'] ?? 0,
               'url' => $result['url'] ?? '',
               'sizes' => $result['sizes'] ?? [],
            ];
            if (!empty($securedFile['metadata'])) {
               $this->applyMeta($securedFile['attachment_id'], $securedFile['metadata']);
            }
            $progress->advance();
         } catch (Exception $e) {
            $progress->failItem($securedFile['upload_id'], $e->getMessage());
            $errors[] = $e->getMessage();
         }
      }
      // Handle destination (meta, post, post_group)
      $this->handleUploadDestination($data, $processedResults);
      // Phase 2: DB registration — quick per-file writes
      foreach ($prepared as $uploadId => $fileInfo) {
         try {
            $result = $uploader->registerFile($fileInfo);
      // Cleanup temp files
            if (!$result['success']) {
               $progress->failItem($uploadId, 'Registration failed');
               $errors[] = "Failed to register {$uploadId}";
               continue;
            }
            $processedResults[$uploadId] = [
               'upload_id'     => $uploadId,
               'attachment_id' => $result['attachment_id'] ?? 0,
               'url'           => $result['url'] ?? '',
               'sizes'         => $result['sizes'] ?? [],
            ];
            // Apply frontend metadata if provided
            $securedFile = $this->findSecuredFile($securedFiles, $uploadId);
            if ($securedFile && !empty($securedFile['metadata'])) {
               $this->applyMeta($result['attachment_id'], $securedFile['metadata']);
            }
            $progress->advance();
         } catch (Exception $e) {
            $progress->failItem($uploadId, $e->getMessage());
            $errors[] = $e->getMessage();
         }
      }
      // Destination + cleanup unchanged
      $this->handleUploadDestination($data, $processedResults);
      $this->cleanupTempFiles($securedFiles, $operation->userId);
      $outcome = count($processedResults) > 0 ? 'success' : 'failed';
@@ -132,49 +148,134 @@
      return new Result(
         outcome: $outcome,
         result: [
            'upload_ids'   => $uploadIds,
            'uploads'      => $processedResults,
            'processed_count'=> count($processedResults),
            'total_count'  => count($uploadIds),
            'errors'    => $errors
            'upload_ids'      => $uploadIds,
            'uploads'         => $processedResults,
            'processed_count' => count($processedResults),
            'total_count'     => count($uploadIds),
            'errors'          => $errors,
         ]
      );
   }
   /**
    * Find a secured file entry by upload_id
    */
   private function findSecuredFile(array $securedFiles, string $uploadId): ?array
   {
      foreach ($securedFiles as $file) {
         if (($file['upload_id'] ?? null) === $uploadId) {
            return $file;
         }
      }
      return null;
   }
   /**
    * Attach upload results to content
    */
   private function processAttachToContent(Operation $operation, array $data, Progress $progress): Result
   {
      $uploadOpId = $data['upload'] ?? null;
      error_log('processing attached to content: '.print_r($data, true));
      if (!$uploadOpId) {
         throw new Exception('No upload operation ID provided');
      }
      // Get results from the dependency
      $uploadOp = JVB()->queue()->get($uploadOpId);
      if (!$uploadOp || $uploadOp->outcome !== 'success') {
      if (!$uploadOp || !in_array($uploadOp->outcome, ['success', 'partial'])) {
         throw new Exception("Upload operation {$uploadOpId} not completed successfully");
      }
      $uploadResults = $uploadOp->result ?? [];
      $uploadResults = $uploadOp->result['uploads'] ?? [];
      if (empty($uploadResults)) {
         throw new Exception('No upload results found');
      }
      // Attach to content via field
      // Resolve post_id from content_update dependency for new posts
      if (empty($data['post_id']) || str_starts_with((string)($data['item_id'] ?? ''), 'new')) {
         foreach ($operation->dependencies as $depId) {
            $dep = JVB()->queue()->get($depId);
            if ($dep && $dep->type === 'content_update' && !empty($dep->result['new_posts'])) {
               $itemId = $data['item_id'] ?? null;
               if ($itemId && isset($dep->result['new_posts'][$itemId])) {
                  $data['post_id'] = $dep->result['new_posts'][$itemId];
                  break;
               }
            }
         }
         if (empty($data['post_id'])) {
            throw new Exception('Could not resolve post_id from dependencies');
         }
      }
      if (!empty($data['field_name'])) {
         $this->updateFieldValue($data, $uploadResults);
         if ($data['field_name'] === 'timeline_gallery') {
            $registrar = Registrar::getInstance($data['content']);
            if ($registrar) {
               switch ($registrar->getType()) {
                  case 'post':
                     $meta = Meta::forPost($data['item_id']);
                     break;
                  case 'term':
                     $meta = Meta::forTerm($data['item_id']);
                     break;
                  case 'user':
                     $meta = Meta::forUser($data['item_id']);
                     break;
                  default:
                     $meta = false;
               }
               if ($meta) {
                  $title = $meta->get('post_title');
                  $current = $meta->get('number');
                  $i = empty($current) ? 1 : $current + 1;
                  foreach ($data['upload_ids'] as $uploadID) {
                     if (!array_key_exists($uploadID, $uploadResults)) {
                        continue;
                     }
                     $imgID = $uploadResults[$uploadID]['attachment_id'] ?? false;
                     if (!$imgID) {
                        continue;
                     }
                     $this->createTimelinePoint($imgID, $data['item_id'], $data['user'], $data['content'], $title, $i);
                     $i++;
                  }
               }
            }
         } else {
            $this->saveToMeta($data, $uploadResults);
         }
      }
      $progress->advance(1);
      return new Result(
         outcome: 'success',
         result: ['attached' => count($uploadResults)]
         result: ['attached' => count($uploadResults), 'post_id' => $data['post_id']]
      );
   }
      protected function createTimelinePoint(int $imgID, int $parentID, int $user, string $postType, string $baseTitle, int $index):int|WP_Error
      {
         $title = $baseTitle.' - Treatment #'.$index;
         $args = [
            'post_type'    => jvbCheckBase($postType),
            'post_author'  => $user,
            'post_status'  => 'draft',
            'post_parent'  => $parentID,
            'post_title'   => $title,
            'post_name'    => sanitize_title($title)
         ];
         $child = wp_insert_post($args);
         if ($child && !is_wp_error($child)) {
            set_post_thumbnail($child, $imgID);
         }
         return $child;
      }
   /**
    * Process metadata updates for attachments
    */
@@ -183,11 +284,13 @@
      $updatedCount = 0;
      $errors = [];
      $postsAttachedTo =[];
      error_log('Processing Meta Update with data: '.print_r($data, true));
      foreach ($data as $uploadId => $info) {
         if (!is_array($info)) {
            continue;
         }
         $success = true;
         try {
            if (array_key_exists('depends_on', $info)) {
               // Get the dependency operation to find attachment ID
@@ -217,9 +320,23 @@
            $progress->advance(1);
         } catch (Exception $e) {
            $success = false;
            $progress->failItem($uploadId, $e->getMessage());
            $errors[] = $e->getMessage();
         }
         if ($success) {
            $postID = wp_get_post_parent_id($attachmentId);
            if ($postID && !in_array($postID, $postsAttachedTo)){
               $postsAttachedTo[] = $postID;
            }
         }
      }
      if (!empty ($postsAttachedTo)) {
         foreach ($postsAttachedTo as $postId) {
            Cache::invalidateItem('post', $postId);
         }
      }
      $outcome = $updatedCount > 0 ? 'success' : 'failed';
@@ -324,7 +441,8 @@
      }
      $content = jvbCheckBase($data['content']);
      if (Features::forContent($data['content'])->has('is_timeline')) {
      $registrar = Registrar::getInstance($data['content']);
      if ($registrar && $registrar->hasFeature('is_timeline')) {
         return $this->processTimelineUploads($operation, $data, $progress, $all_uploads);
      }
@@ -378,14 +496,11 @@
   protected function createPostFromGroup(array $post, int $index, string $content, array $uploads, Operation $op):array|false
   {
      $config = JVB_CONTENT[jvbNoBase($content)]??false;
      if (!$config) {
         throw new Exception('No config found for content: '.$content.'.');
      }
      $registrar = Registrar::getInstance($content);
      $post_title = array_key_exists('post_title', $post['fields'])
         ? sanitize_text_field($post['fields']['post_title'])
         : 'New '. $config['singular'].' '.($index + 1);
         : ($registrar ? 'New '. $registrar->getSingular().' '.($index + 1) : 'New Item '.($index + 1));
      $post_excerpt = array_key_exists('post_excerpt', $post['fields'])
         ? sanitize_textarea_field($post['fields']['post_excerpt'])
@@ -429,11 +544,11 @@
      if (!empty($gallery)) {
         $meta = Meta::forPost($ID);
         $fields = jvbGetFields($content, 'post');
         $fields = Registrar::getFieldsFor($content);
         //add images to first found gallery field
         $found = false;
         foreach ($fields as $name =>$config) {
            if ($config['type'] === 'upload' && (array_key_exists('multiple', $config) && $config['multiple'] === true)) {
            if ($config['type'] === 'gallery' || ($config['type'] === 'upload' && (array_key_exists('multiple', $config) && $config['multiple'] === true))) {
               $found = true;
               $meta->set($name, implode(',', $gallery));
               break;
@@ -442,8 +557,10 @@
         if (!$found) {
            error_log('Could not find a gallery upload field for post '.$ID);
         }
      }
      return [
         'ID'  => $ID,
         'usedUploads' => $uploadIds
@@ -458,9 +575,10 @@
      $errors = [];
      $content = jvbCheckBase($data['content']);
      $config = Features::getConfig($content);
      $registrar = Registrar::getInstance($data['content']);
      $defaultTitle = 'New '.$config['singular']. ' ';
      $defaultTitle = ($registrar) ? 'New '.$registrar->getSingular(). ' ' : 'New Item ';
      foreach($data['posts'] as $index => $post) {
         try {
            $title = array_key_exists('post_title', $post['fields'])
@@ -476,7 +594,7 @@
               'post_author'  => $user,
               'post_status'  => 'draft',
               'post_title'   => $title,
               'post_slug'    => sanitize_title($title),
               'post_name'    => sanitize_title($title),
               'post_excerpt' => $excerpt
            ];
@@ -520,12 +638,10 @@
               foreach($childPosts as $i => $imgID) {
                  $treatment = $i + 1;
                  $args ['post_title'] = $title.' - Treatment #'.$treatment;
                  $child = wp_insert_post($args);
                  if ($child && !is_wp_error($child)) {
                     $createdChildren = $child;
                  $child = $this->createTimelinePoint($imgID, $parent, $args['post_author'], $args['post_type'], $title, $treatment);
                  if ($child && !is_wp_error($child) && $child> 0 ) {
                     $createdChildren[] = $child;
                     $usedUploads[] = $imgID;
                     set_post_thumbnail($child, $imgID);
                  }
               }
            }
@@ -588,22 +704,25 @@
   private function applyMeta(int $attachmentId, array $metadata): void
   {
      if (!empty($metadata['image-title'])) {
         wp_update_post([
            'ID'         => $attachmentId,
            'post_title' => sanitize_text_field($metadata['image-title']),
         ]);
      }
      if (!empty($metadata['image-alt-text'])) {
      if (array_key_exists('image-alt-text', $metadata)) {
         update_post_meta($attachmentId, '_wp_attachment_image_alt', sanitize_text_field($metadata['image-alt-text']));
      }
      if (!empty($metadata['image-caption'])) {
         wp_update_post([
            'ID'           => $attachmentId,
            'post_excerpt' => sanitize_textarea_field($metadata['image-caption']),
         ]);
      $postUpdates = [];
      if (array_key_exists('image-title', $metadata)) {
         $postUpdates['post_title'] = sanitize_text_field($metadata['image-title']);
      }
      if (array_key_exists('image-caption', $metadata)) {
         $postUpdates['post_excerpt'] = sanitize_textarea_field($metadata['image-caption']);
      }
      if (array_key_exists('image-description', $metadata)) {
         $postUpdates['post_excerpt']= sanitize_textarea_field($metadata['image-caption']);
      }
      if (!empty($postUpdates)){
         $postUpdates['ID'] = $attachmentId;
         wp_update_post($postUpdates);
      }
   }
@@ -636,11 +755,19 @@
         return;
      }
      $existing = $meta->get($data['field_name']);
      $existingIds = !empty($existing) ? explode(',', $existing) : [];
      $allIds = array_unique(array_merge($existingIds, $attachmentIds));
      $fieldType = $data['field_type'] ?? 'single';
      $meta->set($data['field_name'], implode(',', $allIds));
      if ($fieldType === 'single') {
         // Single field: replace with latest upload
         $meta->set($data['field_name'], end($attachmentIds));
      } else {
         // Multi field: merge with existing
         $existing = $meta->get($data['field_name']);
         $existingIds = !empty($existing) ? explode(',', $existing) : [];
         $allIds = array_unique(array_merge($existingIds, $attachmentIds));
         $meta->set($data['field_name'], implode(',', $allIds));
      }
   }
   private function updateFieldValue(array $data, array $results): void