Jake Vanderwerf
2025-11-04 42fa8304ddb811b0f725f245130f70c0f5e86a6c
inc/rest/routes/ContentRoutes.php
@@ -5,6 +5,8 @@
use JVBase\rest\RestRouteManager;
use JVBase\managers\CacheManager;
use JVBase\meta\MetaManager;
use JVBase\utility\Features;
use WP_Post;
use WP_Query;
use WP_Error;
use WP_REST_Request;
@@ -185,7 +187,7 @@
        }
        $post_type = str_replace('-', '_',jvbCheckBase($params['content']));
      $config = (array_key_exists($params['content'], JVB_CONTENT) && !empty(JVB_CONTENT[$params['content']])) ? JVB_CONTENT[$params['content']] : [];
      $config = Features::getConfig($params['content']);
@@ -199,9 +201,13 @@
            'author' => $user_id,
            'post_status' => $post_status
        ];
      //Only top level posts for timeline types
      if (Features::forContent($post_type)->has('is_timeline')) {
         $args['post_parent'] = 0;
      }
      //Calendar filters
      if (jvbCheck('is_calendar', $config))  {
      if (Features::forContent($post_type)->has('is_calendar'))  {
         $args = $this->applyCalendarFilters($args, $params);
      }
      if (array_key_exists('taxonomies', $params)) {
@@ -218,27 +224,26 @@
         $args['s'] = sanitize_text_field($params['search']);
      }
      error_log('Content Routes final args: '.print_r($args, true));
        $key = $this->cache->generateKey($args);
      $lastModified = $this->cache->getTimestamp($key);
      if ($lastModified !== false) {
         $headerCheck = $this->ifModifiedSince($lastModified, $args, $request);
         if (!is_null($headerCheck)) {
            return $headerCheck;
         }
      } else {
         // No timestamp yet, but we can still set ETag
         $etag = '"' . md5(serialize($args)) . '"';
         header('ETag: ' . $etag);
         header('Cache-Control: private, max-age=30');
      // 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,
      ]);
      if ($cache_check) {
         return $cache_check;
      }
        $cache = $this->cache->get($key);
      $cache = false;
        if ($cache) {
            return new WP_REST_Response($cache);
            $response = new WP_REST_Response($cache);
         return $this->addCacheHeaders($response);
        }
        // Run query
@@ -260,7 +265,8 @@
        $this->cache->set($key, $data);
        return new WP_REST_Response($data);
        $response = new WP_REST_Response($data);
      return $this->addCacheHeaders($response);
    }
    /**
@@ -306,6 +312,10 @@
        $results = [];
        foreach ($posts as $ID => $post_data) {
         if (Features::forContent($post_data['content'])->has('is_timeline')) {
            $results[$ID] =$this->processTimelinePost($ID, $post_data);
            continue;
         }
         if (str_starts_with($ID, 'new')) {
            error_log('New post detected. Creating... with: '.print_r([
@@ -412,6 +422,97 @@
    }
    /**
    * Extracts the postdata for timeline post child posts from the pseudo-repeater element
    * @param int $parent_id
    * @param array $post_data
    * @return array|true[]
    */
   protected function processTimelinePost(int $parent_id, array $post_data):array
   {
      if (!$this->verifyOwnership($parent_id)) {
         return ['success' => false, 'message' => 'No permission'];
      }
      $rows = $post_data['fields'] ?? [];
      if (empty($rows)) {
         return ['success' => false, 'message' => 'No data'];
      }
      $fields = jvbGetFields($post_data['content']);
      // First row = parent post
      $parent_row = array_shift($rows);
      if (($parent_row['id'] ?? null) != $parent_id) {
         return ['success' => false, 'message' => 'Parent ID mismatch'];
      }
      $allowedFields = array_filter($parent_row, function($key) use ($fields) {
         return array_key_exists($key, $fields);
      }, ARRAY_FILTER_USE_KEY);
      $parentMeta = new MetaManager($parent_id, 'post');
      $parentMeta->setAll($allowedFields);
      // Get existing children to track deletions
      $existing_children = get_children([
         'post_parent' => $parent_id,
         'post_type' => jvbCheckBase($post_data['content']),
         'fields' => 'ids'
      ]);
      $processed_ids = [];
      // Process remaining rows as children
      foreach ($rows as $index => $row_data) {
         $row_id = $row_data['id'] ?? null;
         // New child post
         if (!$row_id || str_starts_with($row_id, 'new')) {
            $child_id = wp_insert_post([
               'post_type' => jvbCheckBase($post_data['content']),
               'post_parent' => $parent_id,
               'post_author' => $this->user_id,
               'post_status' => $post_data['status'] ?? 'draft',
               'menu_order' => $index
            ]);
         }
         // Existing child post
         else {
            $child_id = (int) $row_id;
            // Verify ownership via parent
            if (!in_array($child_id, $existing_children)) {
               continue; // Skip if not actually a child of this parent
            }
            // Update menu_order (position may have changed)
            wp_update_post([
               'ID' => $child_id,
               'menu_order' => $index
            ]);
         }
         // Update child meta
         $allowedChildFields = array_filter($row_data, function($key) use ($fields) {
            return array_key_exists($key, $fields) && $key !== 'id' && $key !== 'draggable';
         }, ARRAY_FILTER_USE_KEY);
         $childMeta = new MetaManager($child_id, 'post');
         $childMeta->setAll($allowedChildFields);
         $processed_ids[] = $child_id;
      }
      // Delete removed children
      $deleted_ids = array_diff($existing_children, $processed_ids);
      foreach ($deleted_ids as $delete_id) {
         wp_delete_post($delete_id, true);
      }
      return ['success' => true, 'processed' => $processed_ids];
   }
    /**
     * Handle batch content creation from uploads
     * @param WP_REST_Request $request
     *
@@ -485,12 +586,15 @@
    }
    /**
     * @param object $post the wordpress post object
     * @param WP_Post $post the wordpress post object
     *
     * @return array
     */
    protected function prepareItem(object $post):array
    protected function prepareItem(WP_Post $post, $skip = false):array
    {
      if (!$skip && Features::forContent($post->post_type)->has('is_timeline')) {
         return $this->formatTimeline($post);
      }
        $this->meta = new MetaManager($post->ID, 'post');
        $data = [
            'id'        => $post->ID,
@@ -520,7 +624,17 @@
            ];
        }
      $images = $this->extractImages();
        if (!empty($images)) {
            $data['images'] = $images;
        }
        return $data;
    }
   protected function extractImages():array
   {
        //Extract images
      $images = [];
      $get = [];
@@ -541,11 +655,40 @@
            }
         }
      }
        if (!empty($images)) {
            $data['images'] = $images;
      return $images;
        }
   protected function formatTimeline(WP_Post $post):array
   {
      $data = $this->prepareItem($post, true);
      $firstRow = $data['fields'];
      $firstRow['id'] = $post->ID;
      $firstRow['draggable'] = false;
      $fields = [$firstRow];
      $children = get_children(['post_parent' => $post->ID, 'orderby' => 'menu_order']);
      $allImages = [];
      foreach ($children as $child) {
         $this->meta = new MetaManager($child->ID, 'post');
         $row = $this->meta->getAll();  // Store in variable first
         $row['id'] = $child->ID;       // Add ID to the row
         $row['draggable'] = true;      // Mark as draggable
         $fields[] = $row;              // Then append to fields
         $images = $this->extractImages();
         if (!empty($images)) {
            $allImages = $allImages + $images;
         }
      }
      if (!empty($allImages)) {
         if (!array_key_exists('images', $data)) {
            $data['images'] = [];
         }
         $data['images'] = $data['images'] + $allImages;
      }
      $data['fields']['timeline'] = $fields;
        return $data;
    }