Jake Vanderwerf
2026-01-25 b38f03c0e7218762d90fa5092696b127f24f36db
inc/rest/routes/ContentRoutes.php
@@ -3,8 +3,10 @@
namespace JVBase\rest\routes;
use JVBase\JVB;
use JVBase\managers\queue\executors\ContentExecutor;
use JVBase\managers\queue\TypeConfig;
use JVBase\rest\RestRouteManager;
use JVBase\managers\CacheManager;
use JVBase\managers\Cache;
use JVBase\meta\MetaManager;
use JVBase\utility\Features;
use WP_Post;
@@ -30,17 +32,38 @@
   protected array $timelineSharedFields = [];
   protected array $timelineUniqueFields = [];
   //TODO: Ensure we are handling the bulk operations for all processes
   //TODO: Also invalidate feed caches on updates!!
   public function __construct()
   {
      $this->cache_name = 'user_content_' . get_current_user_id();
      parent::__construct();
      $this->cache->clear();
      if (JVB_TESTING) {
         $this->cache->flush();
      }
      $this->action = 'dash-';
      $this->operation_type = 'content_update';
      add_filter(BASE . 'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
      add_action('init', [$this, 'registerContentExecutors'], 5);
   }
   /**
    * Register content operation types with the queue's TypeRegistry
    */
   public function registerContentExecutors(): void
   {
      $registry = JVB()->queue()->registry();
      $executor = new ContentExecutor();
      // Content updates - chunked at 10 posts
      $registry->register('content_update', new TypeConfig(
         executor: $executor,
         chunkKey: 'posts',
         chunkSize: 10
      ));
      // Batch creation (from uploads)
      $registry->register('batch_creation', new TypeConfig(
         executor: $executor
      ));
   }
   /**
@@ -143,7 +166,6 @@
   public function handleContentUpdate(WP_REST_Request $request): WP_REST_Response
   {
      $data = $request->get_params();
      error_log('Received data: ' . print_r($data, true));
      $user_id = $data['user'];
      if (!$this->userCheck($user_id)) {
@@ -234,6 +256,7 @@
   {
      $params = $request->get_params();
      $user_id = $params['user'];
      if (!$this->userCheck($user_id)) {
         return new WP_REST_Response([
            'success' => false,
@@ -293,12 +316,10 @@
         $args['s'] = sanitize_text_field($params['search']);
      }
      $key = $this->cache->generateKey($args);
      // Check HTTP cache headers with the specific content type
      $content_type = $params['content'] ?? $params['type'];
      $cache_check = $this->checkHeaders($request, $content_type, [
         'filter_hash' => $key,
      ]);
      $cache_check = $this->checkHeaders($request, $key);
      if ($cache_check) {
         return $cache_check;
      }
@@ -324,7 +345,6 @@
      $this->taxonomies = $this->getTaxonomies($this->post_type);
      $posts = array_map([$this, 'prepareItem'], $query->posts);
      $data = [
         'items' => $posts,
         'total' => $query->found_posts,
@@ -557,11 +577,6 @@
            ];
         }
         CacheManager::for($post_data['content'])->clear();
         if (jvbSiteUsesFeedBlock()) {
            CacheManager::for('feed')->clear();
         }
      }
      if (jvbSiteHasNotifications()) {
@@ -593,6 +608,8 @@
         return ['success' => false, 'message' => 'No permission'];
      }
      error_log('[Processing Timeline Post...');
      $ignore = ['content', 'user'];
      $this->fields = jvbGetFields($post_data['content']);
      $this->initTimelineFields($post_data['content']);
@@ -681,7 +698,8 @@
         ]);
         $prevDate = null;
         $latest_date = null;
         $earliest_date = null;
         foreach ($post_data['timeline'] as $order => $timeline) {
            // Get unique fields for this specific timeline entry
            $allowedFields = array_filter($timeline, function ($key) use ($ignore) {
@@ -760,20 +778,20 @@
            $meta = new MetaManager($timeline['id'], 'post');
            $oldValues = $meta->getAll(array_keys($allowedFields));
            // Set number taxonomy to menu_order (always update for reordering)
            if (!$is_parent) {
               $number_value = $order;
               $term = get_term_by('name', (string)$number_value, BASE . 'number');
               if (!$term) {
                  $result = wp_insert_term((string)$number_value, BASE . 'number');
                  if ($result && !is_wp_error($result)) {
                     $term = $result['term_id'];
                  }
               } else {
                  $term = $term->term_id;
               }
               $allowedFields['number'] = $term;
            }
//          // Set number taxonomy to menu_order (always update for reordering)
//          if (!$is_parent) {
//             $number_value = $order;
//             $term = get_term_by('name', (string)$number_value, BASE . 'number');
//             if (!$term) {
//                $result = wp_insert_term((string)$number_value, BASE . 'number');
//                if ($result && !is_wp_error($result)) {
//                   $term = $result['term_id'];
//                }
//             } else {
//                $term = $term->term_id;
//             }
//             $allowedFields['number'] = $term;
//          }
            // Auto-timeline logic
            if ($prevDate) {
@@ -802,7 +820,7 @@
            $updateValues = array_filter($allowedFields, function ($value, $key) use ($oldValues) {
               return (!array_key_exists($key, $oldValues) || $value !== $oldValues[$key]);
            }, ARRAY_FILTER_USE_BOTH);
            error_log('Setting values for ' . $timeline['id'] . ': ' . print_r($updateValues, true));
            $meta->setAll($updateValues);
            $timeline['id'] = (int)$timeline['id'];
@@ -819,8 +837,8 @@
      }
      if ($clearParent) {
         $this->cache->clear();
         CacheManager::onPostSave($parent_id, $parent_post);
         $this->cache->flush();
         Cache::onPostChange($parent_id, $parent_post);
      }
@@ -1003,7 +1021,7 @@
      }
      $item['fields']['timeline'] = $subFields;
      $item['images'] = $item['images'] + $images;
      $item['number'] = $mainMeta->getValue('number');
      return $item;
   }
@@ -1153,10 +1171,6 @@
//                            $queue->updateOperationProgress($operation->id, $key + 1, $total);
                  }
               }
               //Clear cache
               CacheManager::for($data['content'])->clear();
               CacheManager::for('feed')->clear();
            }
            return [
@@ -1177,4 +1191,121 @@
      return $result;
   }
   // Add to ContentRoutes.php
   /**
    * One-time migration: Set latest_date meta for all timeline posts
    * Call this once via WP-CLI or a temporary admin page
    *
    * Usage: add_action('admin_init', function() {
    *     if (current_user_can('manage_options')) {
    *         JVB()->routes('content')->migrateTimelineLatestDates();
    *     }
    * });
    */
   public function migrateTimelineLatestDates(): array
   {
      global $wpdb;
      $results = [
         'processed' => 0,
         'updated' => 0,
         'skipped' => 0,
         'errors' => []
      ];
      // Get all timeline post types
      $timeline_types = [];
      foreach (JVB_CONTENT as $type => $config) {
         if (Features::forContent($type)->has('is_timeline')) {
            $timeline_types[] = BASE . $type;
         }
      }
      if (empty($timeline_types)) {
         return $results;
      }
      // Get all parent timeline posts
      $args = [
         'post_type' => $timeline_types,
         'post_status' => ['publish', 'draft'],
         'post_parent' => 0,
         'posts_per_page' => -1,
         'fields' => 'ids'
      ];
      $parent_ids = get_posts($args);
      foreach ($parent_ids as $parent_id) {
         $results['processed']++;
         try {
            // Get all children including the parent
            $children = get_children([
               'post_parent' => $parent_id,
               'post_status' => ['publish', 'draft'],
               'orderby' => 'menu_order',
               'order' => 'ASC',
               'fields' => 'ids'
            ]);
            // Add parent to the list
            array_unshift($children, $parent_id);
            // Find latest date among all posts
            $latest_timestamp = 0;
            foreach ($children as $post_id) {
               $date = get_post_meta($post_id, BASE . 'date', true);
               if ($date) {
                  $timestamp = strtotime($date);
                  if ($timestamp > $latest_timestamp) {
                     $latest_timestamp = $timestamp;
                  }
               }
            }
            // Update parent with latest date
            if ($latest_timestamp > 0) {
               update_post_meta($parent_id, BASE . 'latest_date', $latest_timestamp);
               $results['updated']++;
               error_log("Updated post {$parent_id} with latest_date: {$latest_timestamp}");
            } else {
               // Fallback to parent post's post_date
               $parent_post = get_post($parent_id);
               $fallback_timestamp = strtotime($parent_post->post_date);
               if ($fallback_timestamp > 0) {
                  update_post_meta($parent_id, BASE . 'latest_date', $fallback_timestamp);
                  $results['updated']++;
                  error_log("Updated post {$parent_id} with fallback latest_date: {$fallback_timestamp} (from post_date)");
               } else {
                  $results['skipped']++;
                  error_log("No dates found for post {$parent_id}");
               }
            }
         } catch (Exception $e) {
            $results['errors'][] = [
               'post_id' => $parent_id,
               'error' => $e->getMessage()
            ];
         }
      }
      error_log('Timeline migration complete: ' . print_r($results, true));
      return $results;
   }
}
//add_action('init', function() {
////  delete_option('jvb_timeline_migrated');
// if (get_option('jvb_timeline_migrated')) {
//    return;
// }
// JVB()->routes('content')->migrateTimelineLatestDates();
// update_option('jvb_timeline_migrated', true);
//});