Jake Vanderwerf
2026-05-12 16cb63b05910055c31dca821c86f2eb815da99e3
inc/managers/queue/executors/ContentExecutor.php
@@ -3,7 +3,7 @@
use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage};
use JVBase\meta\Meta;
use JVBase\utility\Features;
use JVBase\registrar\Registrar;
use Exception;
if (!defined('ABSPATH')) {
@@ -32,6 +32,7 @@
      if (!in_array($operation->type, self::HANDLED_TYPES)) {
         throw new Exception("ContentExecutor cannot handle type: {$operation->type}");
      }
      error_log('Executing ContentExecutor.php');
      try {
         $data = $operation->requestData;
@@ -66,110 +67,59 @@
   private function processContentUpdate(Operation $operation, array $data, Progress $progress): Result
   {
      $posts = $data['posts'] ?? [];
      error_log('Processing Content Update: '.print_r($posts, true));
      if (empty($posts)) {
         return new Result(
            outcome: 'failed',
            outcome: 'success',
            result: ['message' => 'No posts to update']
         );
      }
      $results = [];
      $results = [
         'errors' => [],
         'success'   => [],
         'newPosts'  => [],
         'timelineParents' => [],
         'timelineStatus'  => [],
         'timelineSharedFields'  => [],
      ];
      $errors = [];
      $timelineParents = [];
      $timelineStatus = [];
      $timelineSharedFields = [];
      foreach ($posts as $id => $postData) {
         try {
            $content = $postData['content'] ?? '';
            // New post creation
            if (str_starts_with((string)$id, 'new')) {
               $newId = wp_insert_post([
                  'post_author' => $this->userId,
                  'post_type'   => jvbCheckBase($content),
                  'post_title'  => $postData['post_title'] ?? '',
                  'post_status' => $postData['status'] ?? 'draft',
               ]);
               if (!$newId || is_wp_error($newId)) {
                  $progress->failItem($id, 'Could not create post');
                  continue;
               }
               $this->savePostFields($newId, $postData);
               $results[$id] = [
                  'success' => true,
                  'new_id' => $newId,
                  'processed_fields' => array_keys($postData)
               ];
               $progress->advance();
               continue;
            $content = $postData['content'] ?? false;
            if (!$content) continue;
            $registrar = Registrar::getInstance($content);
            switch ($registrar->getType()) {
               case 'post':
                  $results = $this->handlePost($id, $postData, $registrar, $results, $progress);
                  break;
               case 'term':
                  $results = $this->handleTerm($id, $postData, $registrar, $results, $progress);
                  break;
               case 'user':
                  $results = $this->handleUser($id, $postData, $registrar, $results, $progress);
                  break;
            }
            // Existing post update
            if (!$this->verifyOwnership((int)$id)) {
               $progress->failItem($id, 'No permission to modify this post');
               $errors[$id] = 'No permission';
               continue;
            }
            $this->savePostFields((int)$id, $postData);
            if (Features::forContent($content)->has('is_timeline')) {
               $post = get_post((int)$id);
               $parentId = $post->post_parent > 0 ? $post->post_parent : $post->ID;
               $sharedFields = array_keys(array_filter(JVB_CONTENT[$content]['fields'], function ($field) {
                  return !array_key_exists('for_all', $field) || !$field['for_all'];
               }));
               if (array_key_exists('post_date', $postData) && !in_array($parentId, $timelineParents)) {
                  $timelineParents[] = $parentId;
               }
               if ($parentId === $id) {
                  if (array_key_exists('post_status', $postData) && !array_key_exists($parentId, $timelineStatus)) {
                     $timelineStatus[$parentId] = $postData['post_status'];
                  }
                  if (count(array_intersect($sharedFields, array_keys($postData))) > 0) {
                     if (!array_key_exists($parentId, $timelineSharedFields)) {
                        $timelineSharedFields[$parentId] = [];
                     }
                     $temp = array_intersect($sharedFields, array_keys($postData));
                     $timelineSharedFields[$parentId] = array_unique(array_merge($timelineSharedFields[$parentId], $temp));
                  }
               }
            }
            $results[$id] = [
               'success' => true,
               'processed_fields' => array_keys($postData)
            ];
            $progress->advance();
         } catch (Exception $e) {
            $progress->failItem($id, $e->getMessage());
            $errors[$id] = $e->getMessage();
            $results[$id] = [
               'success' => false,
               'error' => $e->getMessage()
            ];
            $results['errors'][$id] = $e->getMessage();
         }
      }
      error_log('Final Results: '.print_r($results, true));
      try {
         if (!empty($timelineSharedFields)) {
            $this->checkSharedFields($timelineSharedFields);
         if (!empty($results['timelineSharedFields'])) {
            $this->checkSharedFields($results['timelineSharedFields']);
         }
         if (!empty($timelineStatus)) {
            $this->handleTimelineStatusChange($timelineStatus);
         if (!empty($results['timelineStatus'])) {
            $this->handleTimelineStatusChange($results['timelineStatus']);
         }
         if (!empty($timelineParents)) {
            $this->maybeReorderTimelines($timelineParents);
         if (!empty($results['timelineParents'])) {
            $this->maybeReorderTimelines($results['timelineParents']);
         }
      } catch (Exception $e) {
         $errors[] = $e->getMessage();
         $results['errors'][] = $e->getMessage();
      }
@@ -188,37 +138,87 @@
         $outcome = count($errors) === count($posts) ? 'failed' : 'partial';
      }
      return new Result(
         outcome: $outcome,
         result: [
            'posts' => $results,
            'errors' => $errors,
            'updated_count' => count(array_filter($results, fn($r) => $r['success'] ?? false)),
            'failed_count' => count($errors)
         ]
         result: $results,
      );
   }
   private function savePostFields(int $postId, array $postData): bool
   {
      $content = $postData['content'] ?? '';
      $fields = jvbGetFields($content);
      $fields = Registrar::getFieldsFor($content);
      $allowedFields = array_filter($postData, function ($key) use ($fields) {
         return array_key_exists($key, $fields);
      }, ARRAY_FILTER_USE_KEY);
      //Remove values that are already saved
      $check = Meta::forPost($postId)->getAll(array_keys($allowedFields));
      error_log('Stored values: '.print_r($check, true));
      $allowedFields = array_filter($allowedFields, function ($key) use ($allowedFields, $check) {
         return $allowedFields[$key] !== $check[$key];
      }, ARRAY_FILTER_USE_KEY);
      if (empty($allowedFields)) {
         return true;
      }
      $meta = Meta::forPost($postId);
      $meta->setAll($allowedFields);
      return true;
      return Meta::forPost($postId)
         ->setAll($allowedFields);
   }
   private function saveTermFields(int $termId, array $data): bool
   {
      $content = $data['content'] ?? '';
      error_log('Saving term fields: '.print_r($data, true));
      $fields = Registrar::getFieldsFor($content);
      $allowedFields = array_filter($data, function ($key) use ($fields) {
         return array_key_exists($key, $fields);
      }, ARRAY_FILTER_USE_KEY);
      //Remove values that are already saved
      $check = Meta::forTerm($termId)->getAll(array_keys($allowedFields));
      error_log('Stored values: '.print_r($check, true));
      $allowedFields = array_filter($allowedFields, function ($value, $key) use ($check) {
         error_log('Sent value: '.print_r($value, true));
         error_log('Stored Value: '.print_r($check[$key], true));
         return $value !== $check[$key];
      }, ARRAY_FILTER_USE_BOTH);
      if (empty($allowedFields)) {
         return true;
      }
      error_log('Allowed fields: '.print_r($allowedFields, true));
      return Meta::forTerm($termId)
         ->setAll($allowedFields);
   }
   private function saveUserFields(int $userId, array $data): bool
   {
      $content = $data['content'] ?? '';
      $fields = Registrar::getFieldsFor($content);
      $allowedFields = array_filter($data, function ($key) use ($fields) {
         return array_key_exists($key, $fields);
      }, ARRAY_FILTER_USE_KEY);
      //Remove values that are already saved
      $check = Meta::forUser($userId)->getAll(array_keys($allowedFields));
      $allowedFields = array_filter($allowedFields, function ($key) use ($allowedFields, $check) {
         return $allowedFields[$key] !== $check[$key];
      }, ARRAY_FILTER_USE_KEY);
      if (empty($allowedFields)) {
         return true;
      }
      return Meta::forUser($userId)
         ->setAll($allowedFields);
   }
   // ─────────────────────────────────────────────────────────────
   // Helpers
@@ -242,12 +242,11 @@
         }
      }
   }
   protected function maybeReorderTimeline(int $parentID):void
   protected function getTimelinePosts(int $parentID):array
   {
//    clean_post_cache($parentID);
      $parent = get_post($parentID);
      if (!$parent) {
         return;
         return [];
      }
      $children = get_children([
@@ -258,7 +257,7 @@
      if (count($children) === 0) {
         return;
         return [];
      }
      $allPosts = array_merge([$parent], $children);
@@ -268,6 +267,22 @@
         return strtotime($a->post_date) <=> strtotime($b->post_date);
      });
      return $allPosts;
   }
   protected function maybeReorderTimeline(int $parentID):void
   {
//    clean_post_cache($parentID);
      $parent = get_post($parentID);
      if (!$parent) {
         return;
      }
      $allPosts = $this->getTimelinePosts($parentID);
      if (empty($allPosts)) {
         return;
      }
      // Check if order changed
      $needsReorder = false;
@@ -318,7 +333,7 @@
      foreach ($posts as $index => $post) {
         $meta = Meta::forPost($post->ID);
         if ($index === 0) {
            $meta->set('timeline', '', false);
            $meta->set('timeline', '');
            $previousPost = $post;
            continue; // Parent has no timeline
         }
@@ -329,7 +344,7 @@
            if ($timeline) {
               $termId = $this->getOrCreateTerm($timeline, 'timeline');
               if ($termId) {
                  $success = $meta->set('timeline', $termId, false);
                  $success = $meta->set('timeline', $termId);
               }
            }
         }
@@ -337,7 +352,6 @@
         if ($lastKey === $index) {
            $latestTimestamp = strtotime($post->post_date);
         }
         $previousPost = $post;
      }
@@ -458,8 +472,7 @@
         }
         foreach ($children as $child) {
            $childMeta = Meta::forPost($child);
            $result = $childMeta->setAll($values, false);
            Meta::forPost($child)->setAll($values);
         }
      }
   }
@@ -493,4 +506,138 @@
         }
      }
   }
   protected function handlePost(string|int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array
   {
      // New post creation
      if (str_starts_with((string)$ID, 'new')) {
         $newId = wp_insert_post([
            'post_author' => $this->userId,
            'post_type'   => $registrar->getBased(),
            'post_title'  => $data['post_title'] ?? apply_filters('jvbDefaultTitle', '', $registrar->getSlug()),
            'post_status' => $data['status'] ?? 'draft',
         ]);
         error_log('Created new post: '.print_r($newId, true));
         if (!$newId || is_wp_error($newId)) {
            $results['errors'][$ID] = 'Could not create post';
            $progress->failItem($ID, 'Could not create post');
            return $results;
         }
         $results['newPosts'][$ID] = $newId;
         $this->savePostFields($newId, $data);
         unset($data['content']);
         $results['success'][$newId] = $data;
         $progress->advance();
         return $results;
      }
      //Existing post update
      if (!$this->verifyOwnership((int)$ID)) {
         $progress->failItem($ID, 'No permission to modify this post');
         $results['errors'][$ID] = 'No permission';
         return $results;
      }
      $result = $this->savePostFields((int)$ID, $data);
      unset($data['content']);
      if ($result) {
         $results['success'][$ID] = $data;
      } else {
         $results['errors'][$ID] = 'Could not update post data';
      }
      if ($registrar && $registrar->hasFeature('is_timeline')) {
         $post = get_post((int)$ID);
         $parentId = $post->post_parent > 0 ? $post->post_parent : $post->ID;
         $fields = $registrar->getFields();
         $sharedFields = array_keys(array_filter($fields, function ($field) {
            return !array_key_exists('for_all', $field) || !$field['for_all'];
         }));
         if (array_key_exists('timeline_gallery', $data)) {
            //This should only happen if we delete an image from the gallery
            $changes = explode(',', $data['timeline_gallery']);
            $timelinePosts = $this->getTimelinePosts($parentId);
            if (!empty($timelinePosts)) {
               $posts = array_map(function($item) { return $item->ID; }, $timelinePosts);
               $changed = false;
               foreach ($posts as $tID) {
                  if (!in_array($tID, $changes)) {
                     $changed = true;
                     wp_delete_post($tID, true);
                  }
               }
               if ($changed) {
                  $results['timelineParents'][] = $parentId;
               }
            }
         }
         if (array_key_exists('post_date', $data) && !in_array($parentId, $results['timelineParents'])) {
            $results['timelineParents'][] = $parentId;
         }
         if ($parentId === $ID) {
            if (array_key_exists('post_status', $data) && !array_key_exists($parentId, $results['timelineStatus'])) {
               $results['timelineStatus'][$parentId] = $data['post_status'];
            }
            if (count(array_intersect($sharedFields, array_keys($data))) > 0) {
               if (!array_key_exists($parentId, $results['timelineSharedFields'])) {
                  $results['timelineSharedFields'][$parentId] = [];
               }
               $temp = array_intersect($sharedFields, array_keys($data));
               $results['timelineSharedFields'][$parentId] = array_unique(array_merge($results['timelineSharedFields'][$parentId], $temp));
            }
         }
      }
      $progress->advance();
      return $results;
   }
   protected function handleTerm(int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array
   {
      error_log('Handling term '.$ID.' with data: '.print_r($data, true));
      //Existing term update
      if ($registrar->hasFeature('is_ownable') && (!JVB()->roles()->isOwner($this->userId, $ID) && !JVB()->roles()->isManager($this->userId, $ID))) {
         error_log('Term is ownable. User does not own this term.');
         $progress->failItem($ID, 'No permission to modify this term');
         $results['errors'][$ID] = 'No permission';
         return $results;
      }
      $result = $this->saveTermFields($ID, $data);
      unset($data['content']);
      if ($result) {
         $results['success'][$ID] = $data;
      } else {
         $results['errors'][$ID] = 'Could not update term data';
      }
      $progress->advance();
      return $results;
   }
   protected function handleUser(int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array
   {
      //Existing term update
      if ($ID !== $this->userId || !user_can($this->userId, 'manage_options')) {
         $progress->failItem($ID, 'No permission to modify this term');
         $results['errors'][$ID] = 'No permission';
         return $results;
      }
      $result = $this->saveUserFields($ID, $data);
      unset($data['content']);
      if ($result) {
         $results['success'][$ID] = $data;
      } else {
         $results['errors'][$ID] = 'Could not update post data';
      }
      $progress->advance();
      return $results;
   }
}