| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\managers\CacheManager; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Post; |
| | | use WP_Error; |
| | | use Exception; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | class UserTermsManager |
| | | { |
| | | private string $table_name; |
| | | private CacheManager $cache; |
| | | private string $cacheGroup = 'user_terms_'; |
| | | private int $ttl = DAY_IN_SECONDS; // 1 day default |
| | | protected \wpdb $wpdb; |
| | | private Cache $cache; |
| | | |
| | | protected CustomTable $index; |
| | | |
| | | public function __construct() |
| | | { |
| | | global $wpdb; |
| | | $this->wpdb = $wpdb; |
| | | $this->table_name = $this->wpdb->prefix . BASE . 'user_term_index'; |
| | | $this->defineTables(); |
| | | $this->cache = Cache::for('term_ids')->connect('user'); |
| | | |
| | | // Register hooks |
| | | add_action('save_post', [$this, 'updatePostUserTerms'], 10, 3); |
| | | add_action('before_delete_post', [$this, 'removePostUserTerms']); |
| | | add_action('set_object_terms', [$this, 'handleTermAssignment'], 10, 6); |
| | | |
| | | // Add filter for bulk operation handling |
| | | add_filter(BASE . 'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3); |
| | | } |
| | | |
| | | public function defineTables():void |
| | | { |
| | | $userIndex = CustomTable::for('user_term_index'); |
| | | $userIndex->setColumns([ |
| | | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', |
| | | 'user_id' => "{$userIndex->getUserIDType()} NOT NULL", |
| | | 'term_id' => "{$userIndex->getTermIDType()} NOT NULL", |
| | | 'taxonomy' => 'varchar(32) NOT NULL', |
| | | 'post_count' => 'int(11) NOT NULL DEFAULT 1', |
| | | 'parent_id' => "{$userIndex->getTermIDType()} NOT NULL DEFAULT 0", |
| | | 'last_used' => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', |
| | | ]); |
| | | $userIndex->setKeys([ |
| | | ['key' => 'PRIMARY', 'value' => '(`id`)'], |
| | | ['key' => 'UNIQUE', 'value' => '`user_term` (`user_id`, `term_id`, `taxonomy`)'], |
| | | '`user_taxonomy` (`user_id`, `taxonomy`)', |
| | | '`taxonomy` (`taxonomy`)', |
| | | '`user_id` (`user_id`)', |
| | | '`term_id` (`term_id`)', |
| | | '`parent_id` (`parent_id`)' |
| | | ]); |
| | | $base = BASE; |
| | | $userIndex->setConstraints([ |
| | | "CONSTRAINT `{$base}user_term_user_fk` FOREIGN KEY (`user_id`) |
| | | REFERENCES `{$userIndex->getUserTable()}` (`ID`) ON DELETE CASCADE", |
| | | "CONSTRAINT `{$base}user_term_term_fk` FOREIGN KEY (`term_id`) |
| | | REFERENCES `{$userIndex->getTermTable()}` (`term_id`) ON DELETE CASCADE" |
| | | ]); |
| | | $userIndex->defineTable(); |
| | | $this->index = $userIndex; |
| | | } |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param string|null $taxonomy |
| | |
| | | */ |
| | | public function clearUserCache(int $user_id, string|null $taxonomy = null):void |
| | | { |
| | | $cache = CacheManager::for($user_id.'_term_relationships'); |
| | | if ($taxonomy) { |
| | | $cache->delete(jvbNoBase($taxonomy)); |
| | | } else { |
| | | $cache->invalidate(); |
| | | } |
| | | $cache = Cache::for($user_id.'_term_relationships', DAY_IN_SECONDS)->connect('post', true)->connect('taxonomy', true); |
| | | $cache->flush(); |
| | | } |
| | | |
| | | // Update term usage when a post is saved |
| | | |
| | | /** |
| | | * @param int $post_id |
| | | * @param WP_Post $post |
| | | * @param bool $update |
| | | * |
| | | * @return void |
| | | */ |
| | | public function updatePostUserTerms(int $post_id, WP_Post $post, bool $update):void |
| | | { |
| | | // Skip autosaves and revisions |
| | | if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) { |
| | | return; |
| | | } |
| | | // SAFETY: Skip attachments and other non-content post types |
| | | if (in_array($post->post_type, jvbIgnoredPostTypes())) { |
| | | return; |
| | | } |
| | | |
| | | // Skip non-custom post types |
| | | $post_type = get_post_type($post); |
| | | if (!str_starts_with($post_type, BASE)) { |
| | | return; |
| | | } |
| | | |
| | | $user_id = $post->post_author; |
| | | |
| | | // Get all taxonomies for this post type |
| | | $taxonomies = get_object_taxonomies($post_type); |
| | | |
| | | foreach ($taxonomies as $taxonomy) { |
| | | $terms = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); |
| | | |
| | | if (!is_wp_error($terms) && !empty($terms)) { |
| | | // Add the direct terms |
| | | foreach ($terms as $term_id) { |
| | | $this->updateUserTerm($user_id, $term_id, $taxonomy, false); |
| | | |
| | | // Check if taxonomy is hierarchical and add parent terms |
| | | if (is_taxonomy_hierarchical($taxonomy)) { |
| | | $this->addParentTerms($user_id, $term_id, $taxonomy); |
| | | } |
| | | } |
| | | } |
| | | $this->clearUserCache($user_id, $taxonomy); |
| | | } |
| | | } |
| | | |
| | | // Add all parent terms for a given term |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param int $term_id |
| | | * @param string $taxonomy |
| | | * |
| | | * @return void |
| | | */ |
| | | public function addParentTerms(int $user_id, int $term_id, string $taxonomy):void |
| | | { |
| | | // Get all ancestors (parent terms) |
| | | $ancestors = get_ancestors($term_id, $taxonomy, 'taxonomy'); |
| | | |
| | | if (!empty($ancestors)) { |
| | | foreach ($ancestors as $ancestor_id) { |
| | | $this->updateUserTerm($user_id, $ancestor_id, $taxonomy, true); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Handle term assignment changes |
| | | |
| | | /** |
| | | * Handle term assignment changes |
| | | * @param int $object_id |
| | | * @param array $terms |
| | | * @param array $tt_ids |
| | |
| | | } |
| | | |
| | | $user_id = $post->post_author; |
| | | $is_hierarchical = is_taxonomy_hierarchical($taxonomy); |
| | | |
| | | // Get added terms |
| | | $added_tt_ids = array_diff($tt_ids, $old_tt_ids); |
| | | $added_terms = []; |
| | | |
| | | if (!empty($added_tt_ids)) { |
| | | foreach ($added_tt_ids as $tt_id) { |
| | | $term_id = $this->getTermIDFromTTID($tt_id); |
| | | if ($term_id) { |
| | | $added_terms[] = $term_id; |
| | | } |
| | | } |
| | | |
| | | // Add or increment the new terms |
| | | foreach ($added_terms as $term_id) { |
| | | $this->updateUserTerm($user_id, $term_id, $taxonomy, false); |
| | | |
| | | // Add parent terms for hierarchical taxonomies |
| | | if ($is_hierarchical) { |
| | | $this->addParentTerms($user_id, $term_id, $taxonomy); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Handle removed terms |
| | | $removed_tt_ids = array_diff($old_tt_ids, $tt_ids); |
| | | $removed_terms = []; |
| | | |
| | | if (!empty($removed_tt_ids)) { |
| | | foreach ($removed_tt_ids as $tt_id) { |
| | | $term_id = $this->getTermIDFromTTID($tt_id); |
| | | if ($term_id) { |
| | | $removed_terms[] = $term_id; |
| | | } |
| | | } |
| | | |
| | | // Decrement removed terms |
| | | foreach ($removed_terms as $term_id) { |
| | | $this->decreaseUserTerm($user_id, $term_id, $taxonomy, false); |
| | | |
| | | // Handle parent terms for hierarchical taxonomies |
| | | if ($is_hierarchical) { |
| | | $this->handleParentTermRemoval($user_id, $term_id, $taxonomy); |
| | | } |
| | | } |
| | | } |
| | | $termIDs = array_unique(array_merge($tt_ids, $old_tt_ids)); |
| | | if (!empty($termIDs)) { |
| | | foreach ($termIDs as $termID) { |
| | | $term_id = $this->getTermIDFromTTID($termID); |
| | | $this->updateUserTerm($user_id, $term_id, $taxonomy); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Handles parent term removal logic |
| | | |
| | | /** |
| | | * Updates the entry for the user's term relationship |
| | | * @param int $user_id |
| | | * @param int $term_id |
| | | * @param string $taxonomy |
| | | * |
| | | * @return void |
| | | */ |
| | | public function handleParentTermRemoval(int $user_id, int $term_id, string $taxonomy):void |
| | | { |
| | | // Get parent terms |
| | | $ancestors = get_ancestors($term_id, $taxonomy, 'taxonomy'); |
| | | |
| | | if (!empty($ancestors)) { |
| | | foreach ($ancestors as $ancestor_id) { |
| | | // Check if this parent is still used by other terms |
| | | $still_needed = $this->isParentNeeded($user_id, $ancestor_id, $taxonomy); |
| | | |
| | | if (!$still_needed) { |
| | | $this->decreaseUserTerm($user_id, $ancestor_id, $taxonomy, true); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Check if a parent term is still needed by other terms |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param int $parent_term_id |
| | | * @param string $taxonomy |
| | | * |
| | | * @return bool |
| | | */ |
| | | private function isParentNeeded(int $user_id, int $parent_term_id, string $taxonomy):bool |
| | | public function updateUserTerm(int $user_id, int $term_id, string $taxonomy):bool |
| | | { |
| | | |
| | | |
| | | // Get all direct terms the user has |
| | | $direct_terms = $this->wpdb->get_col($this->wpdb->prepare( |
| | | "SELECT term_id FROM {$this->table_name} |
| | | WHERE user_id = %d |
| | | AND taxonomy = %s |
| | | AND is_parent = 0 |
| | | AND post_count > 0", |
| | | $user_id, |
| | | $taxonomy |
| | | )); |
| | | |
| | | if (empty($direct_terms)) { |
| | | return false; |
| | | } |
| | | |
| | | // Check if any of these terms have the parent as an ancestor |
| | | foreach ($direct_terms as $term_id) { |
| | | if ($term_id == $parent_term_id) { |
| | | continue; // Skip the term itself |
| | | } |
| | | |
| | | $ancestors = get_ancestors($term_id, $taxonomy, 'taxonomy'); |
| | | |
| | | if (in_array($parent_term_id, $ancestors)) { |
| | | return true; // This parent is needed |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | // Remove all term usage records for a post |
| | | |
| | | /** |
| | | * @param int $post_id |
| | | * |
| | | * @return void |
| | | */ |
| | | public function removePostUserTerms(int $post_id):void |
| | | { |
| | | $post = get_post($post_id); |
| | | |
| | | // Skip if not a valid post |
| | | if (!$post || !str_starts_with($post->post_type, BASE)) { |
| | | return; |
| | | } |
| | | |
| | | $user_id = $post->post_author; |
| | | $taxonomies = get_object_taxonomies($post->post_type); |
| | | |
| | | foreach ($taxonomies as $taxonomy) { |
| | | $terms = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); |
| | | $is_hierarchical = is_taxonomy_hierarchical($taxonomy); |
| | | |
| | | if (!is_wp_error($terms) && !empty($terms)) { |
| | | foreach ($terms as $term_id) { |
| | | $this->decreaseUserTerm($user_id, $term_id, $taxonomy, false); |
| | | |
| | | // Handle parent terms for hierarchical taxonomies |
| | | if ($is_hierarchical) { |
| | | $this->handleParentTermRemoval($user_id, $term_id, $taxonomy); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Add or increment a user-term relationship |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param int $term_id |
| | | * @param string $taxonomy |
| | | * @param bool $is_parent |
| | | * |
| | | * @return bool |
| | | */ |
| | | public function updateUserTerm(int $user_id, int $term_id, string $taxonomy, bool $is_parent = false):bool |
| | | { |
| | | |
| | | $taxonomy = jvbCheckBase($taxonomy); |
| | | |
| | | // Ensure the term exists |
| | | $term = get_term($term_id, $taxonomy); |
| | |
| | | return false; |
| | | } |
| | | |
| | | // Insert or update the record |
| | | $result = $this->wpdb->query($this->wpdb->prepare( |
| | | "INSERT INTO {$this->table_name} |
| | | (user_id, term_id, taxonomy, post_count, is_parent, last_used) |
| | | VALUES (%d, %d, %s, 1, %d, NOW()) |
| | | ON DUPLICATE KEY UPDATE |
| | | post_count = post_count + 1, |
| | | is_parent = IF(%d = 1, 1, is_parent), |
| | | last_used = NOW()", |
| | | $user_id, |
| | | $term_id, |
| | | $taxonomy, |
| | | $is_parent ? 1 : 0, |
| | | $is_parent ? 1 : 0 |
| | | )); |
| | | $posts = new WP_Query([ |
| | | 'post_author' => $user_id, |
| | | 'tax_query' => [ |
| | | [ |
| | | 'taxonomy' => $taxonomy, |
| | | 'terms' => $term_id, |
| | | ] |
| | | ], |
| | | 'post_status' => 'publish', |
| | | 'posts_per_page'=> -1, |
| | | 'fields' => 'ids' |
| | | ]); |
| | | $userPosts = count($posts->posts); |
| | | |
| | | // Clear the cache for this user and taxonomy |
| | | $this->clearUserCache($user_id, $taxonomy); |
| | | $result = $this->index->findOrCreate([ |
| | | 'user_id' => $user_id, |
| | | 'term_id' => $term_id, |
| | | 'taxonomy' => $taxonomy |
| | | ],[ |
| | | 'parent_id' => $term->parent, |
| | | 'post_count'=> $userPosts |
| | | ]); |
| | | |
| | | return ($result !== false); |
| | | if ($term->parent > 0) { |
| | | $this->updateUserTerm($user_id, $term->parent, $taxonomy); |
| | | } |
| | | |
| | | return (bool) $result; |
| | | } |
| | | |
| | | // Decrement a user-term relationship |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param int $term_id |
| | | * @param string $taxonomy |
| | | * @param bool $is_parent |
| | | * |
| | | * @return bool |
| | | */ |
| | | public function decreaseUserTerm(int $user_id, int $term_id, string $taxonomy, bool $is_parent = false):bool |
| | | { |
| | | |
| | | |
| | | // Update the record, decrementing the counter |
| | | $this->wpdb->query($this->wpdb->prepare( |
| | | "UPDATE {$this->table_name} |
| | | SET post_count = GREATEST(post_count - 1, 0), |
| | | last_used = NOW() |
| | | WHERE user_id = %d |
| | | AND term_id = %d |
| | | AND taxonomy = %s", |
| | | $user_id, |
| | | $term_id, |
| | | $taxonomy |
| | | )); |
| | | |
| | | // Clean up zero-count records |
| | | $this->wpdb->query($this->wpdb->prepare( |
| | | "DELETE FROM {$this->table_name} |
| | | WHERE user_id = %d |
| | | AND term_id = %d |
| | | AND taxonomy = %s |
| | | AND post_count = 0", |
| | | $user_id, |
| | | $term_id, |
| | | $taxonomy |
| | | )); |
| | | |
| | | // Clear the cache for this user and taxonomy |
| | | $this->clearUserCache($user_id, $taxonomy); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | // Helper function to get term_id from term_taxonomy_id |
| | | |
| | | /** |
| | | * Helper function to get term_id from term_taxonomy_id |
| | | * @param int $tt_id |
| | | * |
| | | * @return int |
| | | */ |
| | | private function getTermIDFromTTID(int $tt_id):int |
| | | { |
| | | |
| | | |
| | | return $this->wpdb->get_var($this->wpdb->prepare( |
| | | "SELECT term_id FROM {$this->wpdb->term_taxonomy} WHERE term_taxonomy_id = %d", |
| | | $tt_id |
| | | )); |
| | | global $wpdb; |
| | | return $wpdb->get_var($wpdb->prepare( |
| | | "SELECT term_id FROM {$wpdb->term_taxonomy} WHERE term_taxonomy_id = %d", |
| | | $tt_id |
| | | )); |
| | | } |
| | | |
| | | // Rebuild the user terms index for all users |
| | | |
| | | /** |
| | | * Rebuild the user terms index for all users |
| | | * @return array |
| | | */ |
| | | public function rebuildAllUserIndex():array |
| | |
| | | |
| | | |
| | | // Clear existing index |
| | | $this->wpdb->query("TRUNCATE TABLE {$this->table_name}"); |
| | | global $wpdb; |
| | | $wpdb->query("TRUNCATE TABLE {$this->index->getFullTableName()}"); |
| | | |
| | | // Get all users with posts |
| | | $users = $this->wpdb->get_col($this->wpdb->prepare( |
| | | "SELECT DISTINCT post_author |
| | | FROM {$this->wpdb->posts} |
| | | WHERE post_status = 'publish' |
| | | AND post_type LIKE %s", |
| | | $this->wpdb->esc_like(BASE) . '%' |
| | | )); |
| | | $users = get_users([ |
| | | 'has_published_posts' => [array_map('jvbCheckBase', Registrar::getRegistered('post'))], |
| | | 'fields' => 'ID' |
| | | ]); |
| | | if (empty($users)) { |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => 'No users found to update' |
| | | ]; |
| | | } |
| | | |
| | | JVB()->queue()->queueOperation( |
| | | 'rebuild_user_term_index', |
| | |
| | | 'users' => $users |
| | | ], |
| | | [ |
| | | 'count' => count($users), |
| | | 'chunk_key' => 'users', |
| | | 'chunk_size' => 5, |
| | | 'operation_id' => 'rebuild_user_terms_' . date('Y_m_d') |
| | |
| | | ]; |
| | | } |
| | | |
| | | // Rebuild the index for a specific user |
| | | |
| | | /** |
| | | * Rebuild the index for a specific user |
| | | * @param int $user_id |
| | | * |
| | | * @return array |
| | | */ |
| | | public function rebuildUserIndex(int $user_id):array |
| | | { |
| | | $user = get_userdata($user_id); |
| | | if (!$user) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'User does not exist' |
| | | ]; |
| | | } |
| | | |
| | | JVB()->queue()->queueOperation( |
| | | 'rebuild_user_term_index', |
| | | 0, |
| | |
| | | ], |
| | | [ |
| | | 'count' => 1, |
| | | 'operation_id' => 'rebuild_user_terms_' . date('Y_m_d') |
| | | 'operation_id' => 'rebuild_user_'.$user_id.'_terms_' . date('Y_m_d') |
| | | ] |
| | | ); |
| | | |
| | |
| | | } |
| | | try { |
| | | |
| | | $results = []; |
| | | foreach ($data['users'] as $user_id) { |
| | | // Clear existing user records |
| | | $this->wpdb->query($this->wpdb->prepare( |
| | | "DELETE FROM {$this->table_name} WHERE user_id = %d", |
| | | $user_id |
| | | )); |
| | | $this->index->delete(['user_id' => $user_id]); |
| | | |
| | | // Clear all caches for this user |
| | | $this->clearUserCache($user_id); |
| | | |
| | | // Get all the user's published posts |
| | | $posts = $this->wpdb->get_col($this->wpdb->prepare( |
| | | "SELECT ID FROM {$this->wpdb->posts} |
| | | WHERE post_author = %d |
| | | AND post_status = 'publish' |
| | | AND post_type LIKE %s", |
| | | $user_id, |
| | | $this->wpdb->esc_like(BASE) . '%' |
| | | )); |
| | | $posts = new WP_Query([ |
| | | 'post_status' => 'publish', |
| | | 'post_type' => array_map('jvbCheckBase', Registrar::getRegistered('post')), |
| | | 'posts_per_page'=> -1, |
| | | 'fields' => 'ids' |
| | | ]); |
| | | |
| | | $processed_terms = 0; |
| | | if (empty($posts->posts)) { |
| | | return [ |
| | | 'success' => true, |
| | | 'result' => [ |
| | | 'user_id' => $user_id, |
| | | 'processed_posts'=> 0, |
| | | 'processed_terms'=> 0, |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | foreach ($posts as $post_id) { |
| | | $post_type = get_post_type($post_id); |
| | | $taxonomies = get_object_taxonomies($post_type); |
| | | $terms = []; |
| | | |
| | | foreach ($taxonomies as $taxonomy) { |
| | | $terms = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); |
| | | $is_hierarchical = is_taxonomy_hierarchical($taxonomy); |
| | | $taxonomies = []; |
| | | $result=[ |
| | | 'user_id' => $user_id, |
| | | 'posts' => count($posts->posts) |
| | | ]; |
| | | foreach ($posts->posts as $postID) { |
| | | $postType = get_post_type($postID); |
| | | if (!array_key_exists($postType, $taxonomies)) { |
| | | $taxonomies[$postType] = get_object_taxonomies($postType); |
| | | } |
| | | $tax = $taxonomies[$postType]; |
| | | $terms = array_unique(array_merge($terms, wp_get_object_terms($postID, $tax, ['fields' => 'ids']))); |
| | | } |
| | | |
| | | if (!is_wp_error($terms) && !empty($terms)) { |
| | | foreach ($terms as $term_id) { |
| | | // Add direct term |
| | | $this->updateUserTerm($user_id, $term_id, $taxonomy, false); |
| | | $processed_terms++; |
| | | $result['terms'] = count($terms); |
| | | |
| | | // Add parent terms for hierarchical taxonomies |
| | | if ($is_hierarchical) { |
| | | $ancestors = get_ancestors($term_id, $taxonomy, 'taxonomy'); |
| | | foreach ($ancestors as $ancestor_id) { |
| | | $this->updateUserTerm($user_id, $ancestor_id, $taxonomy, true); |
| | | $processed_terms++; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | foreach ($terms as $termID) { |
| | | $taxonomy = get_term($termID)->taxonomy??false; |
| | | if (!$taxonomy) { |
| | | continue; |
| | | } |
| | | $this->updateUserTerm($user_id, $termID, $taxonomy); |
| | | } |
| | | |
| | | $results[] = $result; |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'result' => [ |
| | | 'user_id' => $user_id, |
| | | 'processed_posts' => count($posts), |
| | | 'processed_terms' => $processed_terms |
| | | ] |
| | | 'result' => $results |
| | | ]; |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | |
| | | * |
| | | * @return array |
| | | */ |
| | | public function getUserTerms(int $user_id, string $taxonomy, array $args = []):array |
| | | public function fetchUserTerms(int $user_id, string $taxonomy, array $args = []):array |
| | | { |
| | | // Default arguments |
| | | $defaults = [ |
| | | 'orderby' => 'count', // 'count' or 'name' or 'last_used' |
| | | 'order' => 'DESC', // 'DESC' or 'ASC' |
| | | 'limit' => 0, // 0 for no limit |
| | | 'min_count' => 1, // Minimum usage count |
| | | 'include_parents' => true, // Whether to include parent terms |
| | | 'only_direct' => false, // If true, only returns directly-used terms |
| | | 'skip_cache' => false // Whether to skip cache lookup |
| | | ]; |
| | | $args = wp_parse_args($args, $defaults); |
| | | |
| | | // Skip cache and fetch directly |
| | | return $this->fetchUserTerms($user_id, $taxonomy, $args); |
| | | } |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param string $taxonomy |
| | | * @param array $args |
| | | * |
| | | * @return array |
| | | */ |
| | | private function fetchUserTerms(int $user_id, string $taxonomy, array $args):array |
| | | { |
| | | $taxonomy = jvbNoBase($taxonomy); |
| | | if (!in_array($taxonomy, Registrar::getRegistered('term'))){ |
| | | return []; |
| | | } |
| | | $taxonomy = jvbCheckBase($taxonomy); |
| | | $cache = CacheManager::for($user_id.'_term_relationships'); |
| | | $key = $cache->generateKey(array_merge( |
| | | [ |
| | | 'user' => $user_id, |
| | | 'taxonomy' => $taxonomy, |
| | | ], |
| | | $args |
| | | )); |
| | | if (!$args['skip_cache']) { |
| | | $cached = $cache->get($key); |
| | | if ($cached) { |
| | | return $cached; |
| | | } |
| | | } |
| | | |
| | | // Build query |
| | | $query = " |
| | | SELECT ut.term_id, ut.post_count, ut.last_used, ut.is_parent, t.name, t.slug |
| | | FROM {$this->table_name} ut |
| | | JOIN {$this->wpdb->terms} t ON ut.term_id = t.term_id |
| | | WHERE ut.user_id = %d |
| | | AND ut.taxonomy = %s |
| | | AND ut.post_count >= %d |
| | | "; |
| | | $order = array_key_exists('order', $args) ? (!in_array(strtoupper($args['order']), ['ASC', 'DESC']) ? 'DESC' : $args['order']) : 'DESC'; |
| | | $orderby = array_key_exists('orderby', $args) ? (!in_array(strtolower($args['orderby']), ['post_count', 'last_used']) ? 'post_count' : $args['orderby']) : 'post_count'; |
| | | $limit = array_key_exists('limit', $args) ? absint($args['limit']) : 0; |
| | | $limit = $limit === 0 ? null : $limit; |
| | | |
| | | $query_args = [ |
| | | $user_id, |
| | | $taxonomy, |
| | | $args['min_count'] |
| | | ]; |
| | | |
| | | // Add parent term filter if needed |
| | | if ($args['only_direct']) { |
| | | $query .= " AND ut.is_parent = 0"; |
| | | } elseif (!$args['include_parents']) { |
| | | $query .= " AND ut.is_parent = 0"; |
| | | } |
| | | |
| | | // Add ordering |
| | | switch ($args['orderby']) { |
| | | case 'name': |
| | | $query .= " ORDER BY t.name " . ($args['order'] === 'DESC' ? 'DESC' : 'ASC'); |
| | | break; |
| | | case 'last_used': |
| | | $query .= " ORDER BY ut.last_used " . ($args['order'] === 'DESC' ? 'DESC' : 'ASC'); |
| | | break; |
| | | case 'count': |
| | | default: |
| | | $query .= " ORDER BY ut.post_count " . ($args['order'] === 'DESC' ? 'DESC' : 'ASC'); |
| | | break; |
| | | } |
| | | |
| | | // Add limit if specified |
| | | if ($args['limit'] > 0) { |
| | | $query .= " LIMIT %d"; |
| | | $query_args[] = $args['limit']; |
| | | } |
| | | |
| | | // Execute query |
| | | $results = $this->wpdb->get_results( |
| | | $this->wpdb->prepare($query, $query_args), |
| | | ARRAY_A |
| | | ); |
| | | $cache->set($key, $results); |
| | | |
| | | return $results; |
| | | return $this->index->pluck( |
| | | 'term_id', |
| | | [ |
| | | 'user_id' => $user_id, |
| | | 'taxonomy' => $taxonomy |
| | | ], |
| | | $orderby, |
| | | $order, |
| | | $limit |
| | | ); |
| | | } |
| | | |
| | | // Simple function to get just term IDs for a user |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * @param string $taxonomy |
| | | * @param array $args |
| | | * |
| | | * @return array |
| | | */ |
| | | public function getUserTermIDs(int $user_id, string $taxonomy, array $args = []):array |
| | | { |
| | | // Skip cache |
| | | $terms = $this->getUserTerms($user_id, $taxonomy, array_merge($args, ['skip_cache' => true])); |
| | | |
| | | if (empty($terms)) { |
| | | return []; |
| | | } |
| | | |
| | | return array_map(function ($term) { |
| | | return (int)$term['term_id']; |
| | | }, $terms); |
| | | } |
| | | |
| | | /** |
| | | * @param int $user_id |
| | | * |
| | | * @return bool |
| | | */ |
| | | public function warmCache(int $user_id):bool |
| | | { |
| | | // Get all taxonomies |
| | | $taxonomies = getTaxonomies(['_builtin' => false], 'names'); |
| | | |
| | | foreach ($taxonomies as $taxonomy) { |
| | | if (str_starts_with($taxonomy, BASE)) { |
| | | // Pre-cache the most common queries |
| | | $common_args = [ |
| | | // Most frequently used terms |
| | | [ |
| | | 'orderby' => 'count', |
| | | 'order' => 'DESC', |
| | | 'limit' => 20, |
| | | 'include_parents' => true |
| | | ], |
| | | // Recently used terms |
| | | [ |
| | | 'orderby' => 'last_used', |
| | | 'order' => 'DESC', |
| | | 'limit' => 20, |
| | | 'include_parents' => true |
| | | ], |
| | | // Alphabetical list |
| | | [ |
| | | 'orderby' => 'name', |
| | | 'order' => 'ASC', |
| | | 'limit' => 0, |
| | | 'include_parents' => true |
| | | ], |
| | | // Direct terms only (no parents) |
| | | [ |
| | | 'orderby' => 'count', |
| | | 'order' => 'DESC', |
| | | 'limit' => 0, |
| | | 'only_direct' => true |
| | | ] |
| | | ]; |
| | | |
| | | foreach ($common_args as $args) { |
| | | // Force skip_cache to ensure we get fresh data |
| | | $args['skip_cache'] = true; |
| | | |
| | | // Warm the cache by executing the query |
| | | $this->getUserTerms($user_id, $taxonomy, $args); |
| | | } |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | } |