Jake Vanderwerf
2026-05-11 ac444cba221832c012c0435fdc8339fe9f37febb
inc/rest/routes/ContentRoutes.php
@@ -5,18 +5,17 @@
use JVBase\managers\queue\executors\ContentExecutor;
use JVBase\managers\queue\TypeConfig;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
use JVBase\rest\PermissionHandler;
use JVBase\rest\Response;
use JVBase\rest\Rest;
use JVBase\managers\Cache;
use JVBase\rest\Route;
use JVBase\utility\Features;
use WP_Post;
use WP_Query;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use Exception;
use WP_Term;
use WP_Term_Query;
if (!defined('ABSPATH')) {
   exit; // Exit if accessed directly
@@ -43,6 +42,7 @@
         $this->cache->flush();
      }
      $this->cache->connect('post', true);
      $this->cache->connect('term', true);
      add_action('init', [$this, 'registerContentExecutors'], 5);
   }
@@ -96,17 +96,19 @@
            'user'   => 'int|required',
            'posts' => 'required',
            'content' => 'string',
         ]);
         ])
         ->register();
   }
   protected function initTimelineFields(string $content): void
   {
      $content = jvbNoBase($content);
      if (!Features::forContent($content)->has('is_timeline')) {
      $config = Registrar::getInstance($content);
      if (!$config || !$config->hasFeature('is_timeline')) {
         return;
      }
      $config = Features::getConfig($content);
      $this->fields = $config['fields'];
      $this->fields = $config->getFields();
      $this->timelineSharedFields = $this->getTimelineSharedFields($content);
      array_unshift($this->timelineSharedFields, 'post_thumbnail');
@@ -119,11 +121,12 @@
   public function getTimelineUniqueFields(string $content): array
   {
      $content = jvbNoBase($content);
      if (!Features::forContent($content)->has('is_timeline')) {
      $registrar = Registrar::getInstance($content);
      if (!$registrar || !$registrar->hasFeature('is_timeline')) {
         return [];
      }
      $config = Features::getConfig($content);
      $allFields = $config['fields'];
      $allFields = $registrar->getFields();
      return array_keys(array_filter($allFields, function ($field) {
         if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
@@ -136,17 +139,15 @@
   public function getTimelineSharedFields(string $content): array
   {
      $content = jvbNoBase($content);
      if (!Features::forContent($content)->has('is_timeline')) {
      $registrar = Registrar::getInstance($content);
      if (!$registrar || !$registrar->hasFeature('is_timeline')) {
         return [];
      }
      $config = Features::getConfig($content);
      if (!$config || empty($config)) {
         return [];
      }
      $allFields = $config['fields'] ?? [];
      $allFields = $registrar->getFields()??[];
      return array_keys(array_filter($allFields, function ($field) {
         if (!array_key_exists('for_all', $field) || $field['for_all'] === false) {
         if (!array_key_exists('for_all', $field) || is_null($field['for_all']) || $field['for_all'] === false) {
            return true;
         }
         return false;
@@ -175,16 +176,12 @@
      unset($data['id']);
      error_log('[CONTENT]:'.print_r($data, true));
      $queue = JVB()->queue();
      $queue->queueOperation(
         'content_update',
         $user_id,
         $data,
         [
            'count' => $count,
            'chunk_key' => 'posts',
            'chunk_size' => 10,
            'operation_id' => $operationId
         ]
      );
@@ -202,6 +199,24 @@
   public function getContent(WP_REST_Request $request): WP_REST_Response
   {
      $params = $request->get_params();
      error_log('getContent::params '.print_r($params, true));
      $registrar = Registrar::getInstance($params['content']);
      switch ($registrar->getType()) {
         case 'term':
            return $this->getTerms($request, $params, $registrar);
         case 'user':
            //TODO maybe do something?
            break;
         case 'post':
            return $this->getPosts($request, $params, $registrar);
      }
      return $this->error('Something went wrong, this does not appear to have a proper content type');
   }
   public function getPosts(WP_REST_Request $request, array $params, Registrar $registrar):WP_REST_Response
   {
      $user_id = $params['user'];
      $post_status = $params['status'];
@@ -212,7 +227,6 @@
      }
      $post_type = str_replace('-', '_', jvbCheckBase($params['content']));
      // Build query args
      $args = [
         'post_type' => $post_type,
@@ -223,13 +237,15 @@
         'author' => $user_id,
         'post_status' => $post_status
      ];
      //Only top level posts for timeline types
      if (Features::forContent($post_type)->has('is_timeline')) {
      if ($registrar?->hasFeature('is_timeline')) {
         $args['post_parent'] = 0;
      }
      //Calendar filters
      if (Features::forContent($post_type)->has('is_calendar')) {
      if ($registrar?->hasFeature('is_calendar')) {
         $args = $this->applyCalendarFilters($args, $params);
      }
      $taxonomies = array_filter($params, function ($param) {
@@ -256,6 +272,70 @@
         $args['s'] = sanitize_text_field($params['search']);
      }
      $key = $this->cache->generateKey($args);
      $cached = $this->checkCache($key, $request);
      if ($cached) {
         return $cached;
      }
      $this->post_type = jvbCheckBase($params['content'] ?? $params['type']);
      if (array_key_exists('s', $args)) {
         $args = $this->applySearchFilters($args, $params);
      }
      // Run query
      $query = new WP_Query($args);
      $registrar = Registrar::getInstance($this->post_type);
      $this->fields = $registrar->getFields()??[];
      $this->taxonomies = $this->getTaxonomies($this->post_type);
      $posts = array_map([$this, 'preparePost'], $query->posts);
      $data = [
         'items' => $posts,
         'total' => $query->found_posts,
         'total_pages' => $query->max_num_pages,
         'has_more'  => $args['paged']??1 < $query->max_num_pages,
      ];
      $this->cache->set($key, $data);
      $response = Response::success($data);
      return $this->addCacheHeaders($response);
   }
   public function getTerms(WP_REST_Request $request, array $params, Registrar $registrar):WP_REST_Response
   {
      // Build query args
      $args = [
         'taxonomy'     => jvbCheckBase($params['content']),
         'number'    => $params['per_page'] ?? 30,
         'orderby'      => 'name',
         'order'     => 'DESC',
         'hide_empty'   => false,
      ];
      $paged = $params['page']??1;
      $args['page'] = $paged;
      if ($paged > 1) {
         $args['offset'] = ($paged-1) * $args['number'];
      }
      //TODO
//    if (array_key_exists('taxonomies', $params)) {
//       $args = $this->applyTaxonomyFilters($args, $params);
//    }
//    if (array_key_exists('date-filter', $params) || array_key_exists('dateFrom', $params)) {
//       $args = $this->applyDateFilters($args, $params);
//    }
      if (array_key_exists('orderby', $params) || array_key_exists('order', $params)) {
         $args = $this->applyOrderFilters($args, $params);
      }
      if (array_key_exists('search', $params)) {
         $args['s'] = sanitize_text_field($params['search']);
      }
      $key = $this->cache->generateKey($args);
      // Check HTTP cache headers with the specific content type
@@ -269,7 +349,6 @@
         $response = Response::success($cache);
         return $this->addCacheHeaders($response);
      }
      $this->post_type = jvbCheckBase($params['content'] ?? $params['type']);
      // Only expand search to taxonomies if we're actually going to query
      if (array_key_exists('s', $args)) {
@@ -277,20 +356,33 @@
      }
      // Run query
      $query = new WP_Query($args);
      $query = new WP_Term_Query($args);
      $this->fields = jvbGetFields(str_replace('-', '_', $this->post_type));
      $this->taxonomies = $this->getTaxonomies($this->post_type);
      $posts = array_map([$this, 'prepareItem'], $query->posts);
      $terms = $query->get_terms();
      $data = [
         'items' => $posts,
         'total' => $query->found_posts,
         'total_pages' => $query->max_num_pages,
         'has_more'  => $args['paged']??1 < $query->max_num_pages,
         'total'        => 0,
         'total_pages'  => 0,
         'has_more'     => false
      ];
      if (!is_wp_error($terms) && !empty($terms))
      {
         $total = get_terms([
            'taxonomy'     => $args['taxonomy'],
            'hide_empty'   => false,
            'fields'    => 'count'
         ]);
         $data['total'] = $total;
         $data['total_pages'] = max($total/$args['number'], 1);
         $data['has_more'] = ($args['page'] * $args['number']) < $total;
      } else {
         $terms = [];
      }
      $this->fields = $registrar->getFields()??[];
      $this->taxonomies = [];
      $data['items'] =array_map([$this, 'prepareTerm'], $terms);
      $this->cache->set($key, $data);
@@ -387,16 +479,19 @@
    */
   protected function getTaxonomies(string $content): array
   {
      $taxonomy_for = jvbGlobalTaxonomyFor();
      $out = [];
      foreach ($taxonomy_for as $tax => $postTypes) {
         if (in_array($content, $postTypes)) {
            $out[BASE . $tax] = [
               'label' => JVB_CONTENT[$content]['plural'],
               'icon' => $tax,
            ];
         }
      $registrar = Registrar::getInstance($content);
      if (!$registrar || $registrar->getType()!== 'post') {
         return [];
      }
      $out = [];
      foreach ($registrar->registrar->taxonomies as $tax) {
         $taxReg = Registrar::getInstance($tax);
         $out[jvbCheckBase($tax)] = [
            'label'  => $taxReg->getPlural(),
            'icon'   => $taxReg->getIcon()??jvbDefaultIcon()
         ];
      }
      return $out;
   }
@@ -421,13 +516,15 @@
    *
    * @return array
    */
   protected function prepareItem(WP_Post $post, bool $skip = false, bool $fields = true): array
   protected function preparePost(WP_Post $post, bool $skip = false, bool $fields = true): array
   {
      if (!$skip && Features::forContent($post->post_type)->has('is_timeline')) {
      $registrar = Registrar::getInstance($post->post_type);
      if (!$skip && $registrar && $registrar->hasFeature('is_timeline')) {
         $this->initTimelineFields($post->post_type);
         return $this->formatTimeline($post);
      }
      $this->meta = Meta::forPost($post->ID);
      $fields = ($fields) ? $this->meta->getAll() : [];
      $data = [
         'id' => $post->ID,
         'title' => $post->post_title,
@@ -436,66 +533,66 @@
         'modified' => $post->post_modified,
         'thumbnail' => get_the_post_thumbnail_url($post->ID),
         'alt' => get_post_meta(get_post_thumbnail_id(), '_wp_attachment_image_alt', true),
         'icon' => $this->post_type,
         'icon' => $registrar->getIcon(),
         'taxonomies' => [],
         'fields' => ($fields) ? $this->meta->getAll() : [],
         'fields' => $fields,
         'images' => [],
      ];
      // Add taxonomy terms
      foreach ($this->taxonomies as $taxonomy => $options) {
         $tax = str_replace(BASE, '', $taxonomy);
         $terms = wp_get_object_terms(
            $post->ID,
            $taxonomy,
            ['fields' => 'id=>name']
         );
         $data['taxonomies'][$tax] = [
            'terms' => (is_wp_error($terms)) ? [] : $terms,
            'name' => $options['label'],
            'icon' => $tax
         ];
      }
      $images = $this->extractImages();
      $images = $this->extractImages($fields, $this->meta);
      if (!empty($images)) {
         $data['images'] = $images;
      }
      $taxonomies = $this->extractTerms($fields, $this->meta);
      if (!empty($taxonomies)) {
         $data['taxonomies'] = $taxonomies;
      }
      return $data;
   }
   protected function extractImages(array $fields = []): array
   /**
    * @param WP_Term $post the post object
    *
    * @return array
    */
   protected function prepareTerm(WP_Term $post, bool $fields = true): array
   {
      //Extract images
      $images = [];
      $get = [];
      $fields = (empty($fields)) ? $this->fields : $fields;
      foreach ($fields as $field => $config) {
         if ($config['type'] === 'gallery' || $config['type'] === 'image' || $field === 'post_thumbnail') {
            $get[] = $field;
         }
      $registrar = Registrar::getInstance($post->taxonomy);
      $this->meta = Meta::forTerm($post->term_id);
      $fields = ($fields) ? $this->meta->getAll() : [];
      $data = [
         'id' => $post->term_id,
         'title' => $post->name,
         'date' => $fields['created_date']??'',
         'modified' => $fields['modified_date']??'',
         'thumbnail' => '',
         'icon' => $registrar->getIcon(),
         'taxonomies' => [],
         'fields' => $fields,
         'images' => [],
      ];
      $images = $this->extractImages($fields, $this->meta);
      if (!empty($images)) {
         $data['images'] = $images;
      }
      if (!empty($get)) {
         $allImages = $this->meta->getAll($get);
         foreach ($allImages as $k => $imgs) {
            $temp = explode(',', $imgs);
            foreach ($temp as $img) {
               if (is_numeric($img) && !array_key_exists($img, $images)) {
                  $images[$img] = jvbImageData((int)$img);
               }
            }
         }
      $taxonomies = $this->extractTerms($fields, $this->meta);
      if (!empty($taxonomies)) {
         $data['taxonomies'] = $taxonomies;
      }
      return $images;
      error_log('Term data: '.print_r($data, true));
      return $data;
   }
   public function formatTimeline(WP_Post $post): array
   {
      $item = $this->prepareItem($post, true, false);
      $item = $this->preparePost($post, true, false);
      //Step 1: Get the fields that apply to all posts
      $mainMeta = Meta::forPost($post->ID);
      $item['fields'] = $mainMeta->getAll($this->timelineSharedFields);
@@ -514,7 +611,7 @@
         $images[$f['post_thumbnail']] = jvbImageData((int)$f['post_thumbnail']);
      }
      $item['fields']['timeline'] = $subFields;
      $item['fields']['timeline_gallery'] = $subFields;
      $item['images'] = $item['images'] + $images;
      $item['number'] = $mainMeta->get('number');