| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\TaxonomyRelationships; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\UserTermsManager; |
| | | use JVBase\rest\Route; |
| | | use JVBase\utility\Features; |
| | | use JVBase\base\Site; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use Exception; |
| | |
| | | } |
| | | class TermRoutes extends Rest |
| | | { |
| | | use Instance; |
| | | protected object $term_index_manager; |
| | | protected int $per_page; |
| | | |
| | |
| | | { |
| | | $this->cacheName = 'terms'; |
| | | parent::__construct(); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | $this->cache->connect('taxonomy', true); |
| | | $this->cache = Cache::for('terms', 'term'); |
| | | |
| | | |
| | | $this->per_page = 20; |
| | | |
| | | add_action('edited_term', [$this, 'deleteTermPath']); |
| | | add_action('wp_login', [$this, 'clearUserTaxonomyCache'], 10, 2); |
| | | } |
| | | |
| | | /** |
| | |
| | | 'taxonomy' => 'string|required', |
| | | 'name' => 'string|required', |
| | | 'parent' => 'int|default:0', |
| | | ]); |
| | | ]) |
| | | ->register(); |
| | | |
| | | Route::for('terms/check') |
| | | ->get([$this,'getTermDetails']) |
| | | ->auth('public') |
| | | ->rateLimit(); |
| | | ->rateLimit() |
| | | ->register(); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // Add relationship data if requested |
| | | if ($request->get_param('include_relationships')) { |
| | | $relationship_manager = new TaxonomyRelationships(); |
| | | $relationship_manager = TaxonomyRelationships::getInstance(); |
| | | $related_taxonomies = $request->get_param('related_taxonomies') ?: ['jvb_style', 'jvb_theme', 'jvb_city']; |
| | | |
| | | $data['relationships'] = []; |
| | |
| | | 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) { |
| | |
| | | } |
| | | ); |
| | | |
| | | return $this->addCacheHeaders($this->success(['items' => $terms])); |
| | | return $this->success(['items' => $terms]); |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | $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) |
| | |
| | | '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'); |
| | | global $feed_types; |
| | | if (taxIsJVBContentTax($content_type)) { |
| | | $response = $this->getTermsForContentType($request); |
| | | return $this->addCacheHeaders($response); |
| | | $registrar = Registrar::getInstance($content_type); |
| | | if ($registrar->hasFeature('is_content')) { |
| | | //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; |
| | |
| | | // 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 |
| | |
| | | if ($request->get_param('main_context') && in_array(jvbCheckBase(json_decode($request->get_param('main_context'), true)['context']), jvbUserTypes())) { |
| | | |
| | | $main_context = json_decode($request->get_param('main_context'), true); |
| | | $userID = get_post_meta($main_context['id'], BASE.'link', true); |
| | | $manager = new UserTermsManager(); |
| | | $related = $manager->getUserTermIDs($userID, $taxonomy); |
| | | $userID = get_post_meta($main_context['id'], BASE.'profile_link', true); |
| | | $manager = UserTermsManager::getInstance(); |
| | | $related = $manager->fetchUserTerms($userID, $taxonomy); |
| | | |
| | | if (empty($related)) { |
| | | $response = $this->emptyResult(); |
| | | return $this->addCacheHeaders($response); |
| | | return $this->emptyResult(); |
| | | } |
| | | |
| | | $args['include'] = $related; |
| | |
| | | $main_context = json_decode($request->get_param('main_context'), true); |
| | | $thisTaxonomy = str_replace('taxonomy:', '', $main_context['context']); |
| | | $ID = (int)$main_context['id']; |
| | | $manager = new TaxonomyRelationships(); |
| | | $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, |
| | |
| | | $match = $request->get_param('match') ?? 'any'; |
| | | $context = json_decode($request['context'], true); |
| | | |
| | | $relationshipManager = new TaxonomyRelationships(); |
| | | $relationshipManager = TaxonomyRelationships::getInstance(); |
| | | // Prepare array to collect term IDs that match the context |
| | | $related_term_ids = []; |
| | | |
| | |
| | | $args['include'] = $related_term_ids; |
| | | } else { |
| | | // No related terms found, return empty result |
| | | $response = $this->emptyResult(); |
| | | |
| | | return $this->addCacheHeaders($response); |
| | | return $this->emptyResult(); |
| | | } |
| | | } |
| | | |
| | | $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([ |
| | | 'taxonomy' => $taxonomy, |
| | | 'hide_empty' => false, |
| | | 'parent' => $parent |
| | | ]); |
| | | |
| | | // Calculate pagination |
| | | $total_pages = ceil($total_terms / $per_page); |
| | | $has_more = $page < $total_pages; |
| | | |
| | | $key = $this->cache->generateKey($args); |
| | | $cache = $this->cache->get($key); |
| | | return [ |
| | | 'items' => $formatted_terms, |
| | | 'is_hierarchical' => $is_hierarchical, |
| | | 'pagination' => [ |
| | | 'page' => $page, |
| | | 'per_page' => $per_page, |
| | | 'total_pages' => $total_pages, |
| | | 'total_terms' => (int)$total_terms |
| | | ], |
| | | 'has_more' => $has_more |
| | | ]; |
| | | } |
| | | ); |
| | | |
| | | if ($cache) { |
| | | $response = $this->success($cache); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | $formatted_terms = $this->formatTerms($args, $taxonomy); |
| | | // Get total count for pagination |
| | | $total_terms = wp_count_terms([ |
| | | 'taxonomy' => $taxonomy, |
| | | 'hide_empty' => false, |
| | | 'parent' => $parent |
| | | ]); |
| | | |
| | | // Calculate pagination |
| | | $total_pages = ceil($total_terms / $per_page); |
| | | $has_more = $page < $total_pages; |
| | | |
| | | $response = [ |
| | | 'items' => $formatted_terms, |
| | | 'is_hierarchical' => $is_hierarchical, |
| | | 'pagination' => [ |
| | | 'page' => $page, |
| | | 'per_page' => $per_page, |
| | | 'total_pages' => $total_pages, |
| | | 'total_terms' => (int)$total_terms |
| | | ], |
| | | '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 |
| | |
| | | 'has_more' => true, |
| | | ]; |
| | | |
| | | $response = $this->success($response); |
| | | return $this->addCacheHeaders($response); |
| | | return $this->success($response); |
| | | } |
| | | |
| | | |
| | |
| | | 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); |
| | | |
| | |
| | | |
| | | $formatted_terms = []; |
| | | foreach ($terms as $term) { |
| | | $formatted_terms[] = $this->formatSingleTerm($term, $taxonomy, true); |
| | | $formatted_terms[] = $this->formatSingleTerm($term, $taxonomy); |
| | | } |
| | | |
| | | return $formatted_terms; |
| | |
| | | ]; |
| | | |
| | | $data = $this->cache->remember( |
| | | $this->cache->generateKey($args), |
| | | $args, |
| | | function() use ($args, $taxonomy, $page, $per_page) { |
| | | $terms = get_terms($args); |
| | | |
| | |
| | | } |
| | | ); |
| | | |
| | | return $this->addCacheHeaders($this->success($data)); |
| | | return $this->success($data); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 = new TaxonomyRelationships(); |
| | | $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 |
| | |
| | | ]]); |
| | | } |
| | | |
| | | if (Features::forMembership()->has('term_approval')) { |
| | | $approval_routes = JVB()->routes('approvals'); |
| | | $membership = Site::membership(); |
| | | if ($membership && $membership->has('term_approval')) { |
| | | $approval_routes = ApprovalRoutes::getInstance(); |
| | | $request_id = $approval_routes->createTermApprovalRequest( |
| | | $user_id, |
| | | $taxonomy, |
| | |
| | | ], 201); // 201 Created |
| | | |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'terms', |
| | | 'Term creation failed: ' . $e->getMessage(), |
| | | ['user_id' => $user_id, 'taxonomy' => $taxonomy, 'name' => $name] |