<?php
|
namespace JVBase\managers;
|
|
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 Cache $cache;
|
|
protected CustomTable $index;
|
|
public function __construct()
|
{
|
$this->defineTables();
|
$this->cache = Cache::for('term_ids')->connect('user');
|
|
// Register hooks
|
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
|
*
|
* @return void
|
*/
|
public function clearUserCache(int $user_id, string|null $taxonomy = null):void
|
{
|
$cache = Cache::for($user_id.'_term_relationships', DAY_IN_SECONDS)->connect('post', true)->connect('taxonomy', true);
|
$cache->flush();
|
}
|
|
/**
|
* Handle term assignment changes
|
* @param int $object_id
|
* @param array $terms
|
* @param array $tt_ids
|
* @param string $taxonomy
|
* @param bool $append
|
* @param array $old_tt_ids
|
*
|
* @return void
|
*/
|
public function handleTermAssignment(int $object_id, array $terms, array $tt_ids, string $taxonomy, bool $append, array $old_tt_ids):void
|
{
|
$post = get_post($object_id);
|
|
// Skip if not a valid post
|
if (!$post || !str_starts_with($post->post_type, BASE)) {
|
return;
|
}
|
|
$user_id = $post->post_author;
|
|
$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);
|
}
|
}
|
}
|
|
/**
|
* Updates the entry for the user's term relationship
|
* @param int $user_id
|
* @param int $term_id
|
* @param string $taxonomy
|
*
|
* @return bool
|
*/
|
public function updateUserTerm(int $user_id, int $term_id, string $taxonomy):bool
|
{
|
|
$taxonomy = jvbCheckBase($taxonomy);
|
|
// Ensure the term exists
|
$term = get_term($term_id, $taxonomy);
|
if (is_wp_error($term) || empty($term)) {
|
return false;
|
}
|
|
$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);
|
|
$result = $this->index->findOrCreate([
|
'user_id' => $user_id,
|
'term_id' => $term_id,
|
'taxonomy' => $taxonomy
|
],[
|
'parent_id' => $term->parent,
|
'post_count'=> $userPosts
|
]);
|
|
if ($term->parent > 0) {
|
$this->updateUserTerm($user_id, $term->parent, $taxonomy);
|
}
|
|
return (bool) $result;
|
}
|
|
/**
|
* Helper function to get term_id from term_taxonomy_id
|
* @param int $tt_id
|
*
|
* @return int
|
*/
|
private function getTermIDFromTTID(int $tt_id):int
|
{
|
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
|
* @return array
|
*/
|
public function rebuildAllUserIndex():array
|
{
|
|
|
// Clear existing index
|
global $wpdb;
|
$wpdb->query("TRUNCATE TABLE {$this->index->getFullTableName()}");
|
|
$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',
|
0,
|
[
|
'users' => $users
|
],
|
[
|
'chunk_key' => 'users',
|
'chunk_size' => 5,
|
'operation_id' => 'rebuild_user_terms_' . date('Y_m_d')
|
]
|
);
|
|
return [
|
'success' => true,
|
'message' => 'Operation Queued for Processing'
|
];
|
}
|
|
/**
|
* 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,
|
[
|
'users' => [$user_id]
|
],
|
[
|
'count' => 1,
|
'operation_id' => 'rebuild_user_'.$user_id.'_terms_' . date('Y_m_d')
|
]
|
);
|
|
return [
|
'success' => true,
|
'message' => 'Operation Queued for Processing'
|
];
|
}
|
|
/**
|
* Handle bulk operations for notifications
|
*
|
* @param WP_Error|array $result Default result
|
* @param object $operation Operation object
|
* @param array $data Current item
|
*
|
* @return WP_Error|array|bool Operation result
|
*/
|
public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array|bool
|
{
|
if ($operation->type !== 'rebuild_user_term_index') {
|
return $result;
|
}
|
try {
|
|
$results = [];
|
foreach ($data['users'] as $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 = new WP_Query([
|
'post_status' => 'publish',
|
'post_type' => array_map('jvbCheckBase', Registrar::getRegistered('post')),
|
'posts_per_page'=> -1,
|
'fields' => 'ids'
|
]);
|
|
if (empty($posts->posts)) {
|
return [
|
'success' => true,
|
'result' => [
|
'user_id' => $user_id,
|
'processed_posts'=> 0,
|
'processed_terms'=> 0,
|
]
|
];
|
}
|
|
$terms = [];
|
|
$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'])));
|
}
|
|
$result['terms'] = count($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' => $results
|
];
|
} catch (Exception $e) {
|
JVB()->error()->log(
|
'[UserTermsManager]:processOperation',
|
"Exception during operation processing: " . $e->getMessage(),
|
[
|
'operation' => $operation->id,
|
]
|
);
|
return [
|
'success' => false,
|
'result' => $e->getMessage()
|
];
|
}
|
}
|
|
/**
|
* @param int $user_id
|
* @param string $taxonomy
|
* @param array $args
|
*
|
* @return array
|
*/
|
public function fetchUserTerms(int $user_id, string $taxonomy, array $args = []):array
|
{
|
$taxonomy = jvbNoBase($taxonomy);
|
if (!in_array($taxonomy, Registrar::getRegistered('term'))){
|
return [];
|
}
|
$taxonomy = jvbCheckBase($taxonomy);
|
|
$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;
|
|
return $this->index->pluck(
|
'term_id',
|
[
|
'user_id' => $user_id,
|
'taxonomy' => $taxonomy
|
],
|
$orderby,
|
$order,
|
$limit
|
);
|
}
|
|
}
|