Jake Vanderwerf
2026-05-01 48721c85ebcfa973ee81719d2467ca80e4253dc9
inc/rest/routes/FeedRoutes.php
@@ -2,11 +2,11 @@
namespace JVBase\rest\routes;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
use JVBase\rest\Rest;
use JVBase\integrations\Umami;
use JVBase\rest\Route;
use JVBase\utility\Checker;
use JVBase\utility\Features;
use JVBase\base\Site;
use WP_Query;
use WP_Post;
use WP_Term;
@@ -21,7 +21,7 @@
{
   protected int $per_page = 36;
   protected ?Umami $tracker = null;
   protected ?Checker $checker = null;
   protected ?array $fields = null;
   protected ?array $timelineSharedFields = null;
   protected ?array $timelineUniqueFields = null;
@@ -44,9 +44,7 @@
   public function init():void
   {
      $this->checker = Checker::getInstance();
      if (Features::hasIntegration('umami')) {
      if (Site::hasIntegration('umami')) {
         $this->tracker = JVB()->connect('umami');
      }
   }
@@ -95,13 +93,15 @@
            'highlight' => 'string',
         ])
         ->auth('public')
         ->rateLimit(30, 60);
         ->rateLimit(30)
         ->register();
      // Feed types endpoint
      Route::for('feed/types')
         ->get([$this, 'getFeedTypes'])
         ->auth('public')
         ->rateLimit(60, 60);
         ->rateLimit()
         ->register();
   }
   /**
@@ -131,31 +131,34 @@
      return $this->cache->remember(
         $postID,
         function() use ($postID, $type, $metaType, $post, $skip) {
            $config = null;
            $registrar = null;
            switch ($metaType) {
               case 'post':
                  $config = JVB_CONTENT[$type];
                  $registrar = Registrar::getInstance($type);
                  $meta = Meta::forPost($postID);
                  if (!$skip && array_key_exists('is_timeline', $config) && $config['is_timeline']) {
                  if (!$skip && $registrar->isTimeline()) {
                     return $this->formatTimeline($postID, $post);
                  }
                  break;
               case 'term':
                  $meta = Meta::forTerm($postID);
                  $config = JVB_TAXONOMY[$type];
                  $registrar = Registrar::getInstance($type);
                  break;
               case 'user':
                  $meta = Meta::forUser($postID);
                  $registrar = Registrar::getInstance($type);
                  break;
            }
            if (!$config) {
            if (!$registrar) {
               return [];
            }
            $fields = $config['fields'];
            $fields = $registrar->getFields();
            //Allow custom filtering for public fields
            if (array_key_exists('feed', $config) && array_key_exists('fields', $config['feed'])) {
               $fields = array_filter($fields, function($field) use ($config) {
                  return in_array($field, $config['feed']['fields']);
            if (!empty($registrar->getConfig('feed')['fields'])) {
               $fields = array_filter($fields, function($field) use ($registrar) {
                  return in_array($field, $registrar->getConfig('feed')['fields']);
               }, ARRAY_FILTER_USE_KEY);
            }
@@ -186,7 +189,10 @@
            $out['id'] = $postID;
            $out['content'] = $type;
            $out['icon'] = $config['icon']??jvbDefaultIcon();
            $out['icon'] = $registrar->getIcon()??jvbDefaultIcon();
            if ($out['icon'] === '') {
               $out['icon'] = jvbDefaultIcon();
            }
            if ($this->tracker) {
               $args = ($metaType === 'post') ? ['owner_id' => $post->post_author] : [];
@@ -197,7 +203,8 @@
            switch ($metaType) {
               case 'term':
                  $owner = (in_array($type, jvbContentTaxonomies()) ? $meta->getValue('owner') : null);
                  $owner = $registrar->hasFeature('is_content') ? $meta->get('owner') : null;
                  if (!is_null($owner)) {
                     $out['user_id'] = $owner;
                  }
@@ -221,12 +228,11 @@
   protected function initTimelineFields(string $content):void
   {
      $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);
      $this->fields = $config['fields'];
      $this->fields = $registrar->getFields();
      $this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) {
         if (!array_key_exists('for_all', $field) || $field['for_all'] === false){
@@ -287,14 +293,12 @@
         if (empty($value)) {
            continue;
         }
         if (!array_key_exists($key, JVB_TAXONOMY)) {
         $registrar = Registrar::getInstance($key);
         if (!$registrar || $registrar->registrar->public === false){
            continue;
         }
         $taxConfig = JVB_TAXONOMY[$key];
         if (isset($taxConfig['public']) && $taxConfig['public'] === false) {
            continue;
         }
         $terms = array_map('absint', explode(',', $value));
         $terms = array_filter($terms); // Remove 0 values
@@ -334,13 +338,14 @@
   protected function getAuthorData(WP_Post $post)
   {
      $author = $post->post_author;
      $userLink = get_user_meta($author, BASE.'link', true);
      $userLink = get_user_meta($author, BASE.'profile_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'];
            $registrar = Registrar::getInstance($label);
            if ($registrar) {
               $label = $registrar->getSingular();
            } else {
               $label = 'Artist';
            }
@@ -357,16 +362,17 @@
   protected function getTaxonomies(int $postID, string $content): array
   {
      $taxonomies = jvbTaxonomiesForContent($content);
      $registrar = Registrar::getInstance($content)??false;
      $taxonomies = $registrar->registrar->taxonomies;
      $out = [];
      foreach ($taxonomies as $tax) {
         $terms = get_the_terms($postID, $tax);
         $t = [];
         if ($terms && !is_wp_error($terms)) {
            $config = jvbNoBase($tax);
            $config = Registrar::getInstance($tax);
            $out[] = [
               'icon' => $config,
               'title' => JVB_TAXONOMY[$config]['plural'],
               'icon' => $config->getIcon(),
               'title' => $config->getPlural(),
               'terms' => array_map(function ($term) use ($tax, $postID, $content) {
                  $item = $this->cache->remember(
                     $term->term_id,
@@ -396,8 +402,7 @@
      $data = $request->get_params();
      $args = [
         'post_type' => (array_key_exists($data['content'], $this->buildFeedTypesConfig())) ?
            BASE . $data['content'] :
            BASE . array_key_first(JVB_CONTENT),
            jvbCheckBase($data['content']) : null,
         'paged' => intval($data['page'] ?? 1),
         'posts_per_page' => $this->per_page,
      ];
@@ -486,15 +491,8 @@
      // Extract key and value
      $key = array_keys($data['highlight'])[0] ?? false;
      $value = array_values($data['highlight'])[0] ?? false;
      error_log('Highlighted item: ' . $key);
      error_log('Highlighted item: ' . $value);
      error_log('No Single Content Types: ' . print_r(jvbNoSingleContentTypes(), true));
      error_log('Page: ' . print_r($data['paged'], true));
      if (in_array($key, jvbNoSingleContentTypes()) && $value && $data['paged'] === 1) {
         error_log('Formatted Highlighted item: ' . print_r($this->formatItem($value), true));
         error_log('Items: ' . print_r($items, true));
      if ($key && $data['paged'] === 1) {
         array_unshift($items, $this->formatItem($value));
         error_log('Items after unshift: ' . print_r($items, true));
      }
      return $items;
   }
@@ -511,37 +509,29 @@
         return $args;
      }
      $registrar = Registrar::getInstance($context['type']);
      switch (true) {
         case contentIsJVBUserType($context['type']):
         case $registrar->hasFeature('profile_link'):
            $args['author'] = (int)get_post_meta($context['id'], BASE . 'link', true);
            break;
         case taxIsJVBContentTax($context['type']):
         case $registrar->getType() === 'term' && $registrar->hasFeature('is_content'):
            $args['post_type'] = is_array($args['post_type'])
               ? $args['post_type']
               : explode(',', $args['post_type']);
            // Check if filtering global feed content
            if (in_array($context['type'], jvbGlobalFeedContentTaxonomies())) {
            if (in_array(jvbNoBase($context['type']), Registrar::getFeatured('is_content', 'term'))) {
               // Global: show posts from any content type with this taxonomy
               $for_content = JVB_TAXONOMY[$context['type']]['for_content'] ?? [];
               if (empty($for_content)) {
                  // Fall back to any content that has this taxonomy registered
                  $for_content = array_keys(
                     array_filter(
                        JVB_CONTENT,
                        fn($c) => in_array($context['type'], $c['taxonomies'] ?? [])
                     )
                  );
               }
               $for_content = Registrar::getInstance($context['type'])->registrar->for ?? [];
               // Convert to full post types with BASE prefix
               $post_types = array_map(fn($type) => BASE . $type, $for_content);
               $post_types = array_map(fn($type) => jvbCheckBase($type), $for_content);
               // Filter to only show_feed content types
               $show_feed_types = Features::getTypesWithFeature('show_feed', 'content');
               $show_feed_types = Registrar::getFeatured('show_feed', 'post');
               $args['post_type'] = array_intersect(
                  $post_types,
                  array_map(fn($type) => BASE . $type, $show_feed_types)
                  array_map(fn($type) => jvbCheckBase($type), $show_feed_types)
               );
            }
@@ -604,11 +594,11 @@
   {
      $postType = is_array($args['post_type']) ? $args['post_type'][0] : $args['post_type'];
      $slug = jvbNoBase($postType);
      if (Features::forContent($slug)->has('is_timeline')) {
      $registrar = Registrar::getInstance($slug);
      if ($registrar && $registrar->hasFeature('is_timeline')) {
         $args['post_parent'] = 0;
      }
      if (in_array($slug, Features::getTypesWithFeature('is_content', 'taxonomy'))) {
      if ($registrar && $registrar->hasFeature('is_content')) {
         return $this->handleContentTaxonomies($args);
      }
      $args['fields'] = 'ids';
@@ -1135,15 +1125,6 @@
      }
   }
   /**
    * Get custom table fields for a taxonomy
    * @param string $taxonomy Taxonomy type
    * @return array Field definitions
    */
   protected function getCustomTableFields(string $taxonomy): array
   {
      return jvbContentTaxonomiesTableFields($taxonomy)['fields'] ?? [];
   }
   /**
    * Get available feed types (for block editor)
@@ -1152,7 +1133,7 @@
   public function getFeedTypes(WP_REST_Request $request): WP_REST_Response
   {
      // Check HTTP cache
      $cache_check = $this->checkHeaders($request, ['feed_types']);
      $cache_check = $this->checkHeaders($request, 'feed_types');
      if ($cache_check) {
         return $cache_check;
      }
@@ -1172,35 +1153,32 @@
    */
   protected function buildFeedTypesConfig(): array
   {
      if (!$this->checker) {
         $this->checker = Checker::getInstance();
      }
      return $this->cache->remember(
         'contentTypes',
         function () {
            $config = [];
            // Get content types with show_feed
            $contentTypes = Features::getTypesWithFeature('show_feed', 'content');
            $contentTypes = Registrar::getFeatured('show_feed', 'post');
            foreach ($contentTypes as $slug) {
               $this->cache->tag('content:'.$slug);
               $contentConfig = JVB_CONTENT[$slug] ?? null;
               if (!$contentConfig) continue;
               $registrar = Registrar::getInstance($slug);
               if (!$registrar) continue;
               $config[$slug] = [
                  'type' => 'content',
                  'singular' => $contentConfig['singular'] ?? ucfirst($slug),
                  'plural' => $contentConfig['plural'] ?? ucfirst($slug) . 's',
                  'icon' => $slug,
                  'taxonomies' => $this->checker->getTaxonomiesForContent($slug),
                  'singular' => $registrar->getSingular(),
                  'plural' => $registrar->getPlural(),
                  'icon' => $registrar->getIcon(),
                  'taxonomies' => $registrar->registrar->taxonomies,
               ];
            }
            // Get taxonomies with show_feed (content taxonomies)
            $taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy');
            $taxonomies = Registrar::getFeatured('show_feed', 'term');
            foreach ($taxonomies as $slug) {
               $taxConfig = JVB_TAXONOMY[$slug] ?? null;
               if (!$taxConfig || !($taxConfig['is_content'] ?? false)) {
               $registrar = Registrar::getInstance($slug);
               if (!$registrar || !($registrar->hasFeature('is_content') ?? false)) {
                  continue;
               }
@@ -1208,11 +1186,11 @@
               $config[$slug] = [
                  'type' => 'taxonomy',
                  'singular' => $taxConfig['singular'] ?? ucfirst($slug),
                  'plural' => $taxConfig['plural'] ?? ucfirst($slug) . 's',
                  'icon' => $slug,
                  'singular' => $registrar->getSingular(),
                  'plural' => $registrar->getPlural(),
                  'icon' => $registrar->getIcon(),
                  'taxonomies' => [], // Content taxonomies don't have sub-taxonomies
                  'for_content' => $taxConfig['for_content'] ?? [],
                  'for_content' => $registrar->registrar->for ?? []
               ];
            }