Jake Vanderwerf
2026-01-25 b38f03c0e7218762d90fa5092696b127f24f36db
inc/rest/routes/FeedRoutes.php
@@ -1,7 +1,7 @@
<?php
namespace JVBase\rest\routes;
use JVBase\managers\CacheManager;
use JVBase\managers\Cache;
use JVBase\rest\RestRouteManager;
use JVBase\integrations\Umami;
use JVBase\meta\MetaManager;
@@ -32,8 +32,13 @@
      $this->cache_name = 'feed';
      $this->cache_ttl = 86400;
      parent::__construct();
      $this->cache
         ->connect('post')
         ->connect('taxonomy')
         ->connect('user');
      if (JVB_TESTING) {
         $this->cache->clear();
         $this->cache->flush();
      }
   }
@@ -45,28 +50,6 @@
      if (Features::hasIntegration('umami')) {
         $this->tracker = JVB()->connect('umami');
      }
      $this->setupCacheConnections();
   }
   /**
    * Set up cache connections for automatic invalidation
    */
   protected function setupCacheConnections(): void
   {
      // Connect to all content types with show_feed
      $contentTypes = Features::getTypesWithFeature('show_feed', 'content');
      foreach ($contentTypes as $type) {
         CacheManager::for('feed_item_'.$type)->connectTo('post');
         $this->cache->connectTo('post', $type);
      }
      // Connect to all taxonomies with show_feed
      $taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy');
      foreach ($taxonomies as $tax) {
         CacheManager::for('feed_item_'.$tax)->connectTo('taxonomy');
         $this->cache->connectTo('taxonomy', $tax);
      }
   }
   /**
@@ -101,21 +84,20 @@
            $post = get_post($postID);
            $type = jvbNoBase($post->post_type);
            $metaType = 'post';
            $cache = CacheManager::for('feed_item_'.$type);
            break;
         default:
            $post = get_term($postID, jvbCheckBase($type));
            $type = jvbNoBase($type);
            $metaType = 'term';
            $cache = CacheManager::for('feed_item_'.$type);
            break;
      }
      if (!$post || is_wp_error($post)) {
         return [];
      }
//
//    return $cache->remember($postID,
//       function() use ($postID, $type, $metaType, $post, $skip) {
      return $this->cache->remember(
         $postID,
         function() use ($postID, $type, $metaType, $post, $skip) {
            $config = null;
            switch ($metaType) {
               case 'post':
@@ -184,17 +166,19 @@
                     $out['user_id'] = $owner;
                  }
                  $out['url'] = get_term_link($postID, $type);
                  $out['title'] = html_entity_decode($post->name);
                  break;
               case 'post':
                  $out['date'] = $post->post_date;
                  $out['modified'] = $post->post_modified;
                  $out['user_id'] = (int)$post->post_author;
                  $out['url'] = get_the_permalink($postID);
                  $out['title']= get_the_title($postID);
                  break;
            }
//          return $out;
//       }
//    );
      return $out;
            return $out;
         }
      );
   }
@@ -251,13 +235,14 @@
         $item['taxonomies'] = array_merge($item['taxonomies'], $this->extractTaxonomies($f, $postID, jvbNoBase($post->post_type)));
         $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']);
      }
      $item['fields']['number'] = count($children);
      $item['number'] = (int)get_post_meta($post->ID,BASE.'number', true);
      $item['fields']['before'] = get_post_thumbnail_id($children[0]);
      $item['fields']['after'] = get_post_thumbnail_id($children[array_key_last($children)]);
      $item['fields']['timeline'] = $subFields;
      $item['images'] = $item['images'] + $images;
      return $item;
   }
   protected function extractTaxonomies(array $fields, int $postID, string $content):array {
@@ -292,13 +277,12 @@
   protected function formatTaxonomy(WP_Term|int $term, int $postID, string $type)
   {
      $cache = CacheManager::for(jvbNoBase($term->taxonomy));
      return $cache->remember(
         'feed_link_'.$term->term_id,
      return $this->cache->remember(
         $term->term_id,
         function () use ($term, $postID, $type) {
            $base = [
               'ID' => $term->term_id,
               'title' => htmlspecialchars_decode($term->name),
               'title' => html_entity_decode($term->name),
               'url' => get_term_link($term->term_id, $term->taxonomy),
            ];
            if ($this->tracker) {
@@ -313,18 +297,26 @@
   protected function getAuthorData(WP_Post $post)
   {
      $author = $this->cache->get($post->post_author, 'author_data');
      if (!$author) {
         $author = [
            'id' => $post->post_author,
            'label' => 'Artist',
            'value' => get_the_author_meta('display_name', $post->post_author),
            'icon' => 'artist',
            'url' => get_the_permalink(get_user_meta($post->post_author, BASE . 'link', true)),
         ];
         $this->cache->set($post->post_author, $author, 'author_data');
      }
      return $author;
      $author = $post->post_author;
      $userLink = get_user_meta($author, BASE.'link', true);
      return $this->cache->remember(
         $userLink,
         function () use ($userLink, $author) {
            $label = jvbUserRole($author);
            if (array_key_exists($label, JVB_USER)) {
               $label = JVB_USER[$label]['label'];
            } else {
               $label = 'Artist';
            }
            return [
               'id'  => $userLink,
               'label'  => $label,
               'value'  => get_the_title($userLink),
               'icon'   => 'user',
               'url' => get_the_permalink($userLink),
            ];
         }
      );
   }
   protected function getTaxonomies(int $postID, string $content): array
@@ -340,16 +332,23 @@
               'icon' => $config,
               'title' => JVB_TAXONOMY[$config]['plural'],
               'terms' => array_map(function ($term) use ($tax, $postID, $content) {
                  return [
                     'ID' => $term->term_id,
                     'title' => htmlspecialchars_decode($term->name),
                     'url' => get_term_link($term->term_id, $tax),
                     'umami_click' => $this->tracker->trackTaxonomyClick($term->term_id, $tax, [
                        'from' => $content . '_' . $postID
                     ])
                  ];
                  $item = $this->cache->remember(
                     $term->term_id,
                     function() use ($term, $tax, $content, $postID) {
                        return [
                           'ID'  => $term->term_id,
                           'title'  => html_entity_decode($term->name),
                           'url' => get_term_link($term->term_id, $tax),
                        ];
                     }
                  );
                  $item['umami_click'] = $this->tracker->trackTaxonomyClick($term->term_id, $tax, [
                     'from'   => $content.'_'.$postID
                  ]);
                  return $item;
               }, $terms),
            ];
         }
      }
      return $out;
@@ -386,43 +385,43 @@
      return $this->applyFavouritesFilter($args, $data);
   }
   protected function applyTaxonomyFilters(array $args, array $data): array
   {
      if (!isset($data['taxonomy']) || empty($data['taxonomy'])) {
         return $args;
      }
      $taxonomyFilters = $data['taxonomy'];
      // Validate taxonomies exist and sanitize
      $validFilters = [];
      foreach ($taxonomyFilters as $taxonomy => $terms) {
         if (!taxonomy_exists(jvbCheckBase($taxonomy))) {
            continue;
         }
         $validFilters[] = [
            'taxonomy' => jvbCheckBase($taxonomy),
            'field' => 'term_id',
            'terms' => array_map('absint', (array)$terms),
            'operator' => 'IN'
         ];
      }
      if (empty($validFilters)) {
         return $args;
      }
      // Determine relation based on match filter
      $relation = ($data['match'] ?? 'all') === 'all' ? 'AND' : 'OR';
      $args['tax_query'] = array_merge(
         ['relation' => $relation],
         $validFilters
      );
      return $args;
   }
// protected function applyTaxonomyFilters(array $args, array $data): array
// {
//    if (!array_key_exists('taxonomy', $data) || empty($data['taxonomy'])) {
//       return $args;
//    }
//
//    $taxonomyFilters = $data['taxonomy'];
//
//    // Validate taxonomies exist and sanitize
//    $validFilters = [];
//    foreach ($taxonomyFilters as $taxonomy => $terms) {
//       if (!taxonomy_exists(jvbCheckBase($taxonomy))) {
//          continue;
//       }
//
//       $validFilters[] = [
//          'taxonomy' => jvbCheckBase($taxonomy),
//          'field' => 'term_id',
//          'terms' => array_map('absint', (array)$terms),
//          'operator' => 'IN'
//       ];
//    }
//
//    if (empty($validFilters)) {
//       return $args;
//    }
//
//    // Determine relation based on match filter
//    $relation = ($data['match'] ?? 'all') === 'all' ? 'AND' : 'OR';
//
//    $args['tax_query'] = array_merge(
//       ['relation' => $relation],
//       $validFilters
//    );
//
//    return $args;
// }
   /**
    * @param WP_REST_Request $request
@@ -432,19 +431,17 @@
   public function handleFeedRequest(WP_REST_Request $request): WP_REST_Response
   {
      $args = $this->buildRequestArgs($request);
      $cacheContext = $this->buildCacheContext($args, $request);
      $key = $this->cache->generateKey($args);
      // Check HTTP cache headers first
      $cache_check = $this->checkHeaders(
         $request,
         $cacheContext['content_types'],
         $cacheContext['additional_params']
         $key
      );
      if ($cache_check) {
         return $cache_check; // Returns 304 Not Modified
      }
      $key = $this->cache->generateKey($args);
      $cached = $this->cache->get($key);
      if ($cached) {
         if ($request->get_param('highlight')) {
@@ -458,7 +455,7 @@
      // Fetch and format items
      $items = $this->fetchFeedItems($args);
      $ttl = (str_contains($args['orderby'], 'RAND')) ? 1800 : $this->cache_ttl;
      $ttl = (str_contains($args['orderby'], 'RAND')) ? 300 : $this->cache_ttl;
      $this->cache->set($key, $items, $ttl);
      if ($request->get_param('highlight')) {
@@ -1262,6 +1259,7 @@
            // Get content types with show_feed
            $contentTypes = Features::getTypesWithFeature('show_feed', 'content');
            foreach ($contentTypes as $slug) {
               $this->cache->tag('content:'.$slug);
               $contentConfig = JVB_CONTENT[$slug] ?? null;
               if (!$contentConfig) continue;
@@ -1282,6 +1280,8 @@
                  continue;
               }
               $this->cache->tag('taxonomy:'.$slug);
               $config[$slug] = [
                  'type' => 'taxonomy',
                  'singular' => $taxConfig['singular'] ?? ucfirst($slug),