Jake Vanderwerf
2026-08-25 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a
inc/rest/routes/TermRoutes.php
@@ -1,6 +1,9 @@
<?php
namespace JVBase\rest\routes;
use JVBase\Instance;
use JVBase\managers\Cache;
use JVBase\managers\TaxonomyRelationships;
use JVBase\registrar\Registrar;
use JVBase\rest\Rest;
use JVBase\managers\UserTermsManager;
@@ -15,6 +18,7 @@
}
class TermRoutes extends Rest
{
   use Instance;
    protected object $term_index_manager;
    protected int $per_page;
@@ -22,14 +26,14 @@
    {
        $this->cacheName = 'terms';
        parent::__construct();
      $this->cache = Cache::for('terms', 'term');
      if (JVB_TESTING) {
         $this->cache->flush();
      }
      $this->cache->connect('taxonomy', true);
        $this->per_page = 20;
        add_action('edited_term', [$this, 'deleteTermPath']);
        add_action('wp_login', [$this, 'clearUserTaxonomyCache'], 10, 2);
    }
    /**
@@ -88,7 +92,7 @@
        // Add relationship data if requested
        if ($request->get_param('include_relationships')) {
            $relationship_manager = JVB()->termRelationships();
            $relationship_manager = TaxonomyRelationships::getInstance();
            $related_taxonomies = $request->get_param('related_taxonomies') ?: ['jvb_style', 'jvb_theme', 'jvb_city'];
            $data['relationships'] = [];
@@ -122,15 +126,9 @@
   public function getTermDetails(WP_REST_Request $request): WP_REST_Response
   {
      $data = $request->get_params();
      $taxonomies = array_keys($data);
      $cache_check = $this->checkHeaders($request, $this->cache->generateKey(['termDetails' => $data]));
      if ($cache_check) {
         return $cache_check;
      }
      $terms = $this->cache->remember(
         $this->cache->generateKey(['termDetails' => $data]),
         ['termDetails' => $data],
         function() use ($data) {
            $result = [];
            foreach ($data as $tax => $IDs) {
@@ -144,7 +142,7 @@
         }
      );
      return $this->addCacheHeaders($this->success(['items' => $terms]));
      return $this->success(['items' => $terms]);
   }
    /**
@@ -156,11 +154,8 @@
    {
      $data = $request->get_params();
      $taxonomy = sanitize_text_field($data['taxonomy'])??'';
      // Check HTTP cache headers
      $cache_check = $this->checkHeaders($request, $taxonomy);
      if ($cache_check) {
         error_log('Header Check failed');
         return $cache_check;
      if (empty($taxonomy)) {
         return $this->success(['message' => 'No taxonomy selected']);
      }
      // Handle batch request (multiple taxonomies)
@@ -175,41 +170,40 @@
            'include'   => $data['termIDs'],
            'hide_empty'   => true,
         ];
         $key = $this->cache->generateKey($args);
         $cached = $this->cache->get($key);
         if ($cached) {
            $response = $this->success($cached);
            return $this->addCacheHeaders($response);
         $data = $this->cache->remember(
            $args,
            function() use ($args, $taxonomy) {
               return $this->formatTerms($args, $taxonomy);
         }
         );
         $formatted = $this->formatTerms($args, $taxonomy);
         $response = [
            'items'  => $formatted
            'items'  => $data
         ];
         $this->cache->set($key, $response);
         $response = $this->success($response);
         return $this->addCacheHeaders($response);
         return $this->success($response);
      }
      if (array_key_exists('content', $data)) {
         // If content_type is provided, use the specialized endpoint
         $content_type = $request->get_param('content');
         $registrar = Registrar::getInstance($content_type);
         if ($registrar->hasFeature('is_content')) {
            $response = $this->getTermsForContentType($request);
            return $this->addCacheHeaders($response);
            //TODO: Not sure what we're trying for here. Doesn't seem to be used.
            //TODO: Likely for taxonomy content types, like tattoo shops
            return $this->emptyResult();
         }
      }
        $taxonomy = BASE.$request->get_param('taxonomy');
        $taxonomy = jvbCheckBase($request->get_param('taxonomy'));
      if (!taxonomy_exists($taxonomy)) {
         return $this->emptyResult();
      }
        $search = $request->get_param('search');
      $parent = (int)$data['parent']??0;
      $page = max(1, (int)($data['page']??1));
      $per_page = 25;
        if (!taxonomy_exists($taxonomy)) {
         return $this->emptyResult();
        }
        $tax_obj = get_taxonomy($taxonomy);
        $is_hierarchical = $tax_obj->hierarchical;
@@ -217,8 +211,8 @@
        // If searching, handle differently
        if (!empty($search)) {
         error_log('Handling search...');
            $response = $this->handleTermSearch($request);
         return $this->addCacheHeaders($response);
         return $this->handleTermSearch($request);
        }
        // Get terms for current level with child count
@@ -237,12 +231,11 @@
            $main_context = json_decode($request->get_param('main_context'), true);
            $userID = get_post_meta($main_context['id'], BASE.'profile_link', true);
            $manager = new UserTermsManager();
            $manager = UserTermsManager::getInstance();
            $related = $manager->fetchUserTerms($userID, $taxonomy);
            if (empty($related)) {
                $response = $this->emptyResult();
            return $this->addCacheHeaders($response);
            return $this->emptyResult();
            }
            $args['include'] = $related;
@@ -251,12 +244,11 @@
            $main_context = json_decode($request->get_param('main_context'), true);
            $thisTaxonomy = str_replace('taxonomy:', '', $main_context['context']);
            $ID = (int)$main_context['id'];
            $manager = JVB()->termRelationships();
            $manager = TaxonomyRelationships::getInstance();
            $related = $manager->getRelatedTerms($ID, BASE.$request->get_param('taxonomy'));
            if (empty($related)) {
                $response = $this->emptyResult();
            return $this->addCacheHeaders($response);
            return $this->emptyResult();
            }
            $args['tax_query'] = [
                'taxonomy'  => $taxonomy,
@@ -269,7 +261,7 @@
            $match = $request->get_param('match') ?? 'any';
            $context = json_decode($request['context'], true);
            $relationshipManager = JVB()->termRelationships();
            $relationshipManager = TaxonomyRelationships::getInstance();
            // Prepare array to collect term IDs that match the context
            $related_term_ids = [];
@@ -305,22 +297,13 @@
                $args['include'] = $related_term_ids;
            } else {
                // No related terms found, return empty result
                $response = $this->emptyResult();
            return $this->addCacheHeaders($response);
            return $this->emptyResult();
            }
        }
        $key = $this->cache->generateKey($args);
        $cache = $this->cache->get($key);
        if ($cache) {
            $response = $this->success($cache);
         return $this->addCacheHeaders($response);
        }
      $response = $this->cache->remember(
         $args,
         function() use ($args, $taxonomy, $parent, $per_page, $page, $is_hierarchical) {
        $formatted_terms = $this->formatTerms($args, $taxonomy);
        // Get total count for pagination
        $total_terms = wp_count_terms([
@@ -333,7 +316,7 @@
        $total_pages = ceil($total_terms / $per_page);
        $has_more = $page < $total_pages;
        $response = [
            return [
            'items' => $formatted_terms,
            'is_hierarchical' => $is_hierarchical,
            'pagination' => [
@@ -344,10 +327,10 @@
            ],
         'has_more' => $has_more
        ];
         }
      );
        $this->cache->set($key, $response);
        $response = $this->success($response);
      return $this->addCacheHeaders($response);
      return $this->success($response);
    }
   protected function handleBatchTermRequest(string $taxonomy, array $data, WP_REST_Request $request):WP_REST_Response
@@ -384,8 +367,7 @@
         'has_more' => true,
      ];
      $response = $this->success($response);
      return $this->addCacheHeaders($response);
      return $this->success($response);
   }
@@ -398,7 +380,7 @@
   protected function formatTerms(array $args, string $taxonomy): array
   {
      return $this->cache->remember(
         $this->cache->generateKey($args),
         $args,
         function() use ($args, $taxonomy) {
            $terms = get_terms($args);
@@ -408,7 +390,7 @@
            $formatted_terms = [];
            foreach ($terms as $term) {
               $formatted_terms[] = $this->formatSingleTerm($term, $taxonomy, true);
               $formatted_terms[] = $this->formatSingleTerm($term, $taxonomy);
            }
            return $formatted_terms;
@@ -478,7 +460,7 @@
      ];
      $data = $this->cache->remember(
         $this->cache->generateKey($args),
         $args,
         function() use ($args, $taxonomy, $page, $per_page) {
            $terms = get_terms($args);
@@ -508,7 +490,7 @@
         }
      );
      return $this->addCacheHeaders($this->success($data));
      return $this->success($data);
   }
    /**
@@ -550,167 +532,6 @@
        delete_term_meta($termID, BASE.'term_path');
    }
    /**
     * @param string $username
     * @param object $user
     *
     * @return void
     */
    public function clearUserTaxonomyCache(string $username, object $user):void
    {
        global $wpdb;
        // Get all transients for this user
        $like_pattern = $wpdb->esc_like(BASE.'user_' . $user->ID) . '_%';
        $transients = $wpdb->get_col($wpdb->prepare("
            SELECT option_name
            FROM {$wpdb->options}
            WHERE option_name LIKE %s
            AND option_name LIKE %s
        ", '_transient_' . $like_pattern, '%'));
        // Delete all found transients
        foreach ($transients as $transient) {
            delete_transient(str_replace('_transient_', '', $transient));
        }
    }
    /**
     * @param WP_REST_Request $request
     *
     * @return WP_REST_Response
     */
    public function getTermsForContentType(WP_REST_Request $request):WP_REST_Response
    {
        $manager = JVB()->termRelationships();
        $content_type = BASE . $request->get_param('content');
        $taxonomy = BASE . $request->get_param('taxonomy');
        $search = $request->get_param('search');
        $page = max(1, intval($request->get_param('page')));
        $per_page = $request->get_param('per_page') ?: 20;
        // Create cache key
        $cache_key = "terms_for_{$content_type}_{$taxonomy}_" . md5("{$search}_{$page}_{$per_page}");
        $cache_key = $this->cache->generateKey([
            'terms_for'     => $content_type,
            'taxonomy'      => $taxonomy,
            'search'        => $search,
            'page'          => $page,
            'per_page'      => $per_page
        ]);
        $cache = $this->cache->get($cache_key);
        // Try cache first
        if ($cache !== false) {
         $response = $this->success($cache);
            return $this->addCacheHeaders($response);
        }
        try {
            global $wpdb;
            // Starting the query building
            $query_args = [];
            $terms_query = "
                SELECT DISTINCT t.term_id, t.name, t.slug, tt.count, tt.taxonomy,
                       (SELECT COUNT(DISTINCT tm.meta_key) FROM {$wpdb->termmeta} tm WHERE tm.term_id = t.term_id) AS meta_count
                FROM {$wpdb->terms} t
                JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
                JOIN {$manager->getTableName()} tr ON (
                    (tr.primary_taxonomy = %s AND tr.primary_term_id = t.term_id)
                    OR (tr.related_taxonomy = %s AND tr.related_term_id = t.term_id)
                )
            ";
            $query_args[] = $taxonomy;
            $query_args[] = $taxonomy;
            // Add content type filter through posts
            $query_where = " WHERE tt.taxonomy = %s";
            $query_args[] = $taxonomy;
            // Add search condition if provided
            if ($search) {
                $query_where .= " AND (t.name LIKE %s OR t.slug LIKE %s)";
                $search_like = '%' . $wpdb->esc_like($search) . '%';
                $query_args[] = $search_like;
                $query_args[] = $search_like;
            }
            // Add relationship strength ordering
            $query_order = " GROUP BY t.term_id";
            $query_order .= " ORDER BY COUNT(tr.id) DESC, tt.count DESC";
            // Add pagination
            $query_limit = " LIMIT %d OFFSET %d";
            $query_args[] = $per_page;
            $query_args[] = ($page - 1) * $per_page;
            // Combine the query
            $final_query = $query_where . $query_order . $query_limit;
            // Get terms
            $terms = $wpdb->get_results(
                $wpdb->prepare(
                    $terms_query . $final_query,
                    $query_args
                )
            );
            // Get total count for pagination
            $count_query = str_replace($query_limit, '', $query_where);
            $count_query = "SELECT COUNT(DISTINCT t.term_id) FROM {$wpdb->terms} t
                           JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
                           JOIN {$manager->getTableName()} tr ON (
                               (tr.primary_taxonomy = %s AND tr.primary_term_id = t.term_id)
                               OR (tr.related_taxonomy = %s AND tr.related_term_id = t.term_id)
                           ) " . $count_query;
            // Remove pagination args
            array_pop($query_args);
            array_pop($query_args);
            $total = $wpdb->get_var(
                $wpdb->prepare(
                    $count_query,
                    $query_args
                )
            );
            // Format terms
            $formatted_terms = [];
            $is_hierarchical = is_taxonomy_hierarchical($taxonomy);
         foreach ($terms as $term) {
            $formatted = $this->formatSingleTerm($term, $taxonomy, false);
            // Add relationship strength which is unique to this method
            $formatted['relationship_strength'] = $term->relationship_count ?? 0;
            $formatted_terms[] = $formatted;
         }
            // Build response
            $total_pages = ceil($total / $per_page);
            $results = [
                'items' => $formatted_terms,
                'pagination'    => [
                    'page'      => (int)$page,
                    'per_page'  => (int)$per_page,
                    'total_terms'=> $total,
                    'total_pages'=> $total_pages
                ],
            'has_more'  => $page < $total_pages
            ];
            // Cache results
            $this->cache->set($cache_key, $results);
         $response = $this->success($results);
            return $this->addCacheHeaders($response);
        } catch (Exception $e) {
         return $this->error('Error getting terms for content: '.$e->getMessage(), 'get_terms_for_content');
        }
    }
    /**
     * @param WP_REST_Request $request