From b38f03c0e7218762d90fa5092696b127f24f36db Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 25 Jan 2026 07:07:26 +0000
Subject: [PATCH] =Some logical flaws in Queue.php, Queue.js, ContentExecutor.php, UploadExecutor.php - particularly with timeline ordering, frontend queue updates, etc
---
inc/managers/CacheManagerOld.php | 930 ++++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 705 insertions(+), 225 deletions(-)
diff --git a/inc/managers/CacheManagerOld.php b/inc/managers/CacheManagerOld.php
index 38297f6..d1cf099 100644
--- a/inc/managers/CacheManagerOld.php
+++ b/inc/managers/CacheManagerOld.php
@@ -2,30 +2,296 @@
namespace JVBase\managers;
if (!defined('ABSPATH')) {
- exit; // Exit if accessed directly
+ exit;
}
-class CacheManagerOld
+/**
+ * Manages HTTP cache timestamps and relationship-based invalidation
+ *
+ * Data caching: Use wrapper methods or wp_cache_get/set directly
+ * HTTP caching: This class manages timestamps for ETag/Last-Modified headers
+ */
+class CacheManager
{
- private string $prefix = 'jvb_';
+ private const CONNECTIONS_OPTION = BASE.'cache_connections';
+ private static ?array $connections_cache = null; // Cache in memory
+ private string $prefix = BASE;
private string $group;
private int $cache_ttl;
private static ?bool $use_object_cache = null;
+ private static array $instances = []; // Cache instances per type
+ private static array $http_timestamps = []; // Request-level memory cache
+ private static ?CacheManager $singleton = null;
/**
- * @param string|null $group The group name for this cache instance
- * @param int|null $ttl The default ttl for this instance
+ * Private constructor - use for() factory method instead
*/
- public function __construct(?string $group = null, ?int $ttl = null)
+ private function __construct(string $group, ?int $ttl = null)
{
- $this->group = $group ?: 'jvb_default';
+ $this->group = jvbNoBase($group);
$this->cache_ttl = $ttl ?: 3600;
- // Check if Redis/Memcached is available
if (is_null(static::$use_object_cache)) {
- static::$use_object_cache = !is_null(wp_using_ext_object_cache());
-// error_log((static::$use_object_cache) ? 'Using Object Cache' : 'Not using Object Cache');
+ static::$use_object_cache = wp_using_ext_object_cache();
}
+
+ add_action('init', [$this, 'registerHooks']);
+ }
+
+ /**
+ * Get singleton instance (for general cache operations)
+ * For type-specific operations, use for() or forUser() instead
+ */
+ public static function getInstance(): self
+ {
+ if (self::$singleton === null) {
+ self::$singleton = new self('global', HOUR_IN_SECONDS);
+ }
+ return self::$singleton;
+ }
+
+ /**
+ * Get all cache connections (public accessor)
+ *
+ * @return array Array of cache group connections
+ */
+ public static function getAllConnections(): array
+ {
+ return self::getConnections();
+ }
+
+ /**
+ * Get all registered cache groups
+ *
+ * @return array List of cache group names
+ */
+ public static function getAllGroups(): array
+ {
+ $connections = self::getConnections();
+ return array_keys($connections);
+ }
+ /**
+ * Register WordPress hooks for automatic cache invalidation
+ * Call this once during plugin initialization
+ */
+ public static function registerHooks(): void
+ {
+ // Post updates (all post types including core)
+ add_action('save_post', [self::class, 'onPostSave'], 10, 2);
+ add_action('delete_post', [self::class, 'onPostDelete']);
+ // Meta updates (will catch MetaManager updates)
+ add_action('updated_post_meta', [self::class, 'onPostMetaUpdate'], 10, 4);
+ add_action('added_post_meta', [self::class, 'onPostMetaUpdate'], 10, 4);
+ add_action('deleted_post_meta', [self::class, 'onPostMetaDelete'], 10, 4);
+ // transition_post_status?
+
+ // Term updates (all taxonomies)
+ add_action('edited_term', [self::class, 'onTermSave'], 10, 3);
+ add_action('create_term', [self::class, 'onTermSave'], 10, 3);
+ add_action('delete_term', [self::class, 'onTermDelete'], 10, 3);
+
+ // Term meta updates
+ add_action('updated_term_meta', [self::class, 'onTermMetaUpdate'], 10, 4);
+ add_action('added_term_meta', [self::class, 'onTermMetaUpdate'], 10, 4);
+ add_action('deleted_term_meta', [self::class, 'onTermMetaDelete'], 10, 4);
+
+ // User updates
+ add_action('profile_update', [self::class, 'onUserUpdate'], 10, 2);
+ add_action('user_register', [self::class, 'onUserUpdate'], 10, 1);
+ add_action('deleted_user', [self::class, 'onUserDelete']);
+
+ // User meta updates
+ add_action('updated_user_meta', [self::class, 'onUserMetaUpdate'], 10, 4);
+ add_action('added_user_meta', [self::class, 'onUserMetaUpdate'], 10, 4);
+ add_action('deleted_user_meta', [self::class, 'onUserMetaDelete'], 10, 4);
+ }
+
+ /**
+ * Get or create a cache manager instance for a content type
+ *
+ * @param string $type Content type (tattoo, style, etc.)
+ * @param int|null $ttl Optional TTL override
+ * @return self Fluent interface
+ */
+ public static function for(string $type, ?int $ttl = null): self
+ {
+ $type = jvbNoBase($type);
+ $key = $type . ($ttl ? "_{$ttl}" : '');
+
+ if (!isset(self::$instances[$key])) {
+ self::$instances[$key] = new self($type, $ttl);
+ }
+
+ return self::$instances[$key];
+ }
+
+ /**
+ * Get cache manager for a specific user
+ * Each user gets their own cache group for complete isolation
+ *
+ * @param int $user_id User ID
+ * @param int|null $ttl Optional TTL
+ * @return self
+ */
+ public static function forUser(int $user_id, ?int $ttl = null): self
+ {
+ return self::for("user_{$user_id}", $ttl);
+ }
+
+ /**
+ * Get HTTP cache timestamp for content type(s)
+ * Used for ETag and Last-Modified header generation
+ *
+ * @param string|array $types Single type or array of types
+ * @return int Latest timestamp (Unix time)
+ */
+ public static function getTimestamp(string|array $types): int
+ {
+ // Multiple types - return latest
+ if (is_array($types)) {
+ $latest = 0;
+ foreach ($types as $type) {
+ $timestamp = self::getTimestamp($type);
+ if ($timestamp > $latest) {
+ $latest = $timestamp;
+ }
+ }
+ return $latest ?: time();
+ }
+
+ $type = jvbNoBase($types);
+
+ // Check request-level cache
+ if (isset(self::$http_timestamps[$type])) {
+ return self::$http_timestamps[$type];
+ }
+
+ // Load from cache (Redis or transient - wp_cache handles it)
+ $timestamp = (int)wp_cache_get("http_ts_{$type}", 'jvb_timestamps') ?: time();
+
+ // Cache in memory for this request
+ self::$http_timestamps[$type] = $timestamp;
+
+ return $timestamp;
+ }
+
+ /**
+ * Update HTTP cache timestamp (marks content as modified)
+ *
+ * @param string $type Content type
+ * @return int The new timestamp
+ */
+ public static function updateTimestamp(string $type): int
+ {
+ $type = jvbNoBase($type);
+ $timestamp = time();
+
+ // Store (Redis or transient - wp_cache handles it)
+ wp_cache_set("http_ts_{$type}", $timestamp, 'jvb_timestamps', WEEK_IN_SECONDS);
+
+ // Update request cache
+ self::$http_timestamps[$type] = $timestamp;
+
+ do_action('jvb_http_timestamp_updated', $type, $timestamp);
+
+ return $timestamp;
+ }
+
+ /**
+ * Invalidate cache for a content type
+ *
+ * @param string $type Content type to invalidate
+ * @param string|array|null $specific_keys Optional specific key(s) to delete without flushing group
+ * @param bool $flush_connections Whether to flush connected caches
+ * @return void
+ */
+ public static function invalidateAll(string $type, $specific_keys = null, bool $flush_connections = true): void
+ {
+ $type = jvbNoBase($type);
+
+ // Update HTTP timestamp
+ self::updateTimestamp($type);
+
+ // If specific keys provided, only delete those
+ if ($specific_keys !== null) {
+ $instance = self::for($type);
+ if (is_array($specific_keys)) {
+ foreach ($specific_keys as $key) {
+ $instance->delete($key);
+ }
+ } else {
+ $instance->delete($specific_keys);
+ }
+ } else {
+ // Flush the entire group
+ if (function_exists('wp_cache_flush_group')) {
+ wp_cache_flush_group($type);
+ } else {
+ wp_cache_flush();
+ }
+ }
+
+ // Flush connected caches
+ if ($flush_connections) {
+ self::for($type)->connections();
+ }
+
+ do_action('jvb_cache_invalidated', $type);
+ }
+
+ /**
+ * Invalidate only specific keys for a type (doesn't flush group or update timestamp)
+ * Use this when you want surgical cache invalidation
+ *
+ * @param string $type Content type
+ * @param string|array $keys Key(s) to delete
+ * @return void
+ */
+ public static function invalidateKeys(string $type, string|array $keys): void
+ {
+ $instance = self::for($type);
+
+ if (is_array($keys)) {
+ foreach ($keys as $key) {
+ $instance->delete($key);
+ }
+ } else {
+ $instance->delete($keys);
+ }
+ }
+
+ /**
+ * Fluent instance method to invalidate this cache type
+ *
+ * @param string|array|null $specific_keys Optional specific key(s)
+ * @param bool $flush_connections Whether to flush connected caches
+ * @return self For chaining
+ */
+ public function invalidate($specific_keys = null, bool $flush_connections = true): self
+ {
+ self::invalidateAll($this->group, $specific_keys, $flush_connections);
+ return $this;
+ }
+
+ /**
+ * Get the HTTP timestamp for this instance's type
+ *
+ * @return int
+ */
+ public function timestamp(): int
+ {
+ return self::getTimestamp($this->group);
+ }
+
+ /**
+ * Update the HTTP timestamp for this instance's type
+ *
+ * @return self For chaining
+ */
+ public function touch(): self
+ {
+ self::updateTimestamp($this->group);
+ return $this;
}
/**
@@ -37,39 +303,17 @@
public function get(string|array $key, ?string $group = null): mixed
{
$group = $group ?: $this->group;
-
$key = $this->normalizeKey($key);
-
$cache_key = $this->buildKey($key);
- // Use appropriate cache method
- if (static::$use_object_cache) {
- $value = wp_cache_get($cache_key, $group);
- } else {
- // Fallback to transients for local development
- $value = get_transient($this->getTransientKey($cache_key, $group));
+ $value = wp_cache_get($cache_key, $group);
+
+ // Fallback to transient if no external object cache
+ if ($value === false && !wp_using_ext_object_cache()) {
+ $value = get_transient($group . '_' . $cache_key);
}
- return (is_array($value) && array_key_exists('data', $value)) ? $value['data'] : $value;
- }
-
- public function getTimestamp(string|array $key, ?string $group = null): mixed
- {
- $group = $group ?: $this->group;
-
- $key = $this->normalizeKey($key);
-
- $cache_key = $this->buildKey($key);
-
- // Use appropriate cache method
- if (static::$use_object_cache) {
- $value = wp_cache_get($cache_key, $group);
- } else {
- // Fallback to transients for local development
- $value = get_transient($this->getTransientKey($cache_key, $group));
- }
-
- return (is_array($value) && array_key_exists('last_modified', $value)) ? $value['last_modified'] : false;
+ return $value;
}
/**
@@ -84,25 +328,21 @@
{
$ttl = $ttl ?: $this->cache_ttl;
$group = $group ?: $this->group;
-
$key = $this->normalizeKey($key);
-
$cache_key = $this->buildKey($key);
- $temp = [
- 'data' => $value,
- 'last_modified' => time(),
- ];
- $value = $temp;
- // Use appropriate cache method
- if (static::$use_object_cache) {
- return wp_cache_set($cache_key, $value, $group, $ttl);
- } else {
- // Fallback to transients
- return set_transient($this->getTransientKey($cache_key, $group), $value, $ttl);
+ self::updateTimestamp($this->group);
+
+ // Try object cache first
+ $result = wp_cache_set($cache_key, $value, $group, $ttl);
+
+ // If no external object cache, also store in transient for persistence
+ if (!wp_using_ext_object_cache()) {
+ set_transient($group . '_' . $cache_key, $value, $ttl);
}
- }
+ return $result;
+ }
/**
* Delete a cached value
* @param string|array $key The key to look up (auto-generates key from array of key=>values)
@@ -112,147 +352,60 @@
public function delete(string|array $key, ?string $group = null): bool
{
$group = $group ?: $this->group;
-
$key = $this->normalizeKey($key);
-
$cache_key = $this->buildKey($key);
- // Use appropriate cache method
- if (static::$use_object_cache) {
- return wp_cache_delete($cache_key, $group);
- } else {
- return delete_transient($this->getTransientKey($cache_key, $group));
+ $result = wp_cache_delete($cache_key, $group);
+
+ // Also delete transient if no external object cache
+ if (!wp_using_ext_object_cache()) {
+ delete_transient($group . '_' . $cache_key);
}
+
+ return $result;
}
- public function clear():bool
+
+ /**
+ * Clear all cache for this group
+ * @return bool
+ */
+ public function clear(): bool
{
try {
- if (static::$use_object_cache) {
- // With Redis, this could be implemented with SCAN command
- // but wp_cache_* doesn't expose this, so we'd need direct Redis access
- // For now, just flush the group as a nuclear option
- if (function_exists('wp_cache_flush_group')) {
- wp_cache_flush_group($this->group);
- return true;
- }
- return false;
- } else {
- // For transients, search and delete
- global $wpdb;
-
- $prefix = self::getTransientPrefix($this->group);
- $sql = "SELECT option_name FROM {$wpdb->options}
- WHERE option_name LIKE %s
- AND option_name LIKE %s";
-
- $keys = $wpdb->get_col($wpdb->prepare(
- $sql,
- '_transient_' . $prefix . '%'
- ));
-
- foreach ($keys as $key) {
- $transient_key = str_replace('_transient_', '', $key);
- delete_transient($transient_key);
- }
- return true;
+ if (function_exists('wp_cache_flush_group')) {
+ wp_cache_flush_group($this->group);
}
- } catch (\Exception $e) {
- } finally {
+ // Clear transients for this group if no external object cache
+ if (!wp_using_ext_object_cache()) {
+ $this->clearGroupTransients();
+ }
+
+ self::updateTimestamp($this->group);
+ return true;
+ } catch (\Exception $e) {
return false;
}
}
/**
- * Alias for delete() for backwards compatibility
- * @param string $key The key to look up (auto-generates key from array of key=>values)
- * @param string|null $group The group to delete from (defaults to current group))
- * @return void
+ * Clear all transients for this cache group
*/
- public function invalidate(string $key, ?string $group = null): void
+ private function clearGroupTransients(): void
{
- $this->delete($key, $group);
- }
+ global $wpdb;
- /**
- * Clear all cache entries for a group
- * @param string $group The group to clear
- * @return bool
- */
- public static function invalidateGroup(string $group): bool
- {
- $group = jvbNoBase($group);
+ $pattern = '_transient_' . $this->group . '_' . $this->prefix . '%';
+ $timeout_pattern = '_transient_timeout_' . $this->group . '_' . $this->prefix . '%';
- if (wp_using_ext_object_cache()) {
- // With Redis/Memcached, use native group flush
- if (function_exists('wp_cache_flush_group')) {
- return wp_cache_flush_group($group);
- } else {
- // Fallback for older WP versions - flush everything (not ideal)
- return wp_cache_flush();
- }
- } else {
- // For transients, we need to delete them from database
- global $wpdb;
-
- $prefix = self::getTransientPrefix($group);
-
- // Delete transients and their timeouts
- $sql = "DELETE FROM {$wpdb->options}
- WHERE option_name LIKE %s
- OR option_name LIKE %s";
-
- $result = $wpdb->query($wpdb->prepare(
- $sql,
- '_transient_' . $prefix . '%',
- '_transient_timeout_' . $prefix . '%'
- ));
-
- return $result !== false;
- }
- }
-
- /**
- * Clear cache entries by pattern (only works efficiently with Redis)
- * @param string $pattern
- * @return int
- */
- public function clearPattern(string $pattern): int
- {
- $count = 0;
-
- if (static::$use_object_cache) {
- // With Redis, this could be implemented with SCAN command
- // but wp_cache_* doesn't expose this, so we'd need direct Redis access
- // For now, just flush the group as a nuclear option
- if (function_exists('wp_cache_flush_group')) {
- wp_cache_flush_group($this->group);
- return $count;
- }
- } else {
- // For transients, search and delete
- global $wpdb;
-
- $prefix = self::getTransientPrefix($this->group);
- $sql = "SELECT option_name FROM {$wpdb->options}
- WHERE option_name LIKE %s
- AND option_name LIKE %s";
-
- $keys = $wpdb->get_col($wpdb->prepare(
- $sql,
- '_transient_' . $prefix . '%',
- '%' . $pattern . '%'
- ));
-
- foreach ($keys as $key) {
- $transient_key = str_replace('_transient_', '', $key);
- delete_transient($transient_key);
- $count++;
- }
- }
-
- return $count;
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s",
+ $pattern,
+ $timeout_pattern
+ )
+ );
}
/**
@@ -289,22 +442,18 @@
{
$group = $group ?: $this->group;
$ttl = $ttl ?: $this->cache_ttl;
-
$key = $this->normalizeKey($key);
$value = $this->get($key, $group);
+
if ($value === false) {
$value = $callback();
- if ($value !== false) {
- $value = [
- 'data' => $value,
- 'last_modified' => time(),
- ];
+ if ($value !== false && $value !== null) {
$this->set($key, $value, $ttl, $group);
}
}
- return (is_array($value) && array_key_exists('data', $value)) ? $value['data']: $value;
+ return $value;
}
/**
@@ -318,59 +467,390 @@
}
/**
- * Get transient key for fallback mode
- * @param string $key
- * @param string $group
- * @return string
+ * Get instance group name (for debugging)
*/
- private function getTransientKey(string $key, string $group): string
+ public function getGroup(): string
{
- // Transients have a 172 character limit
- $full_key = $group . '_' . $key;
+ return $this->group;
+ }
- if (strlen($full_key) > 160) {
- // Use hash for long keys, but keep group prefix for clearPattern()
- return substr($group, 0, 20) . '_' . md5($full_key);
+
+ /***************************************************************************
+ * CONNECTIONS
+ * Connect to other caches by instantiating and defining connection
+ * Ex: CacheManager::for('usernames')->connectTo($type, $scope = 'all', $keyPattern)
+ * Where: $type = content / taxonomy / user
+ * $scope = either 'id' for specific item, or the entire group (registered post type, taxonomy, or user role)
+ * $keyPattern = ??
+ ***************************************************************************/
+ /**
+ * Define a connection between cache groups
+ * Connected caches will have their ID-based keys deleted when this cache invalidates
+ *
+ * @param string $type Grand overview ('post', 'taxonomy', 'user')
+ * @param string $scope Type-specific constant, user role, or 'id'
+ * @return self For chaining
+ */
+ public function connectTo(string $type, string $scope = 'id'): self
+ {
+ //TODO: Handle connect to where $type === 'all'
+ $connections = self::getConnections();
+
+ if (!isset($connections[$this->group])) {
+ $connections[$this->group] = [];
}
- return $full_key;
- }
+ $new_connection = [
+ 'parent' => $type,
+ 'scope' => $scope
+ ];
- /**
- * Get transient prefix for a group
- */
- private static function getTransientPrefix(string $group): string
- {
- return $group . '_jvb_';
- }
-
- /**
- * Check if using object cache
- */
- public function isUsingObjectCache(): bool
- {
- return static::$use_object_cache;
- }
-
-
- /**
- * Cleanup expired transients (maintenance method for non-Redis environments)
- */
- public static function cleanupExpiredTransients(): int
- {
- if (wp_using_ext_object_cache()) {
- return 0; // Not needed with Redis
+ // Check if already exists
+ foreach ($connections[$this->group] as $existing) {
+ if ($existing === $new_connection) {
+ return $this;
+ }
}
- global $wpdb;
+ $connections[$this->group][] = $new_connection;
+ update_option(self::CONNECTIONS_OPTION, $connections, false);
+ self::$connections_cache = $connections;
- // Delete expired transients
- $sql = "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
- WHERE a.option_name LIKE '_transient_%'
- AND a.option_name NOT LIKE '_transient_timeout_%'
- AND b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
- AND b.option_value < %d";
+ return $this;
+ }
- return $wpdb->query($wpdb->prepare($sql, time()));
+ /**
+ * Get all registered connections (cached for performance)
+ *
+ * @param bool $refresh Force refresh from database
+ * @return array
+ */
+ private static function getConnections(bool $refresh = false): array
+ {
+ if (self::$connections_cache === null || $refresh) {
+ self::$connections_cache = get_option(self::CONNECTIONS_OPTION, []);
+ }
+
+ return self::$connections_cache;
+ }
+
+ /**
+ * Flush all caches connected to this one
+ *
+ * @return self For chaining
+ */
+ public function connections(): self
+ {
+ $all_connections = self::getConnections();
+
+ foreach ($all_connections as $cache_group => $connections) {
+ foreach ($connections as $conn) {
+ if ($this->matchesConnection($conn)) {
+ $this->flushConnection($cache_group, $conn);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Check if this cache group matches a connection definition
+ */
+ private function matchesConnection(array $connection): bool
+ {
+ $parent = $connection['parent'] ?? '';
+ $scope = $connection['scope'] ?? 'id';
+
+ // Grand overview match
+ if ($this->group === $parent) {
+ return true;
+ }
+
+ // Type-specific match
+ if ($scope !== 'id') {
+ if ($this->group === jvbNoBase($scope)) {
+ return true;
+ }
+
+ // Check constants
+ if ($parent === 'post' && defined('JVB_CONTENT')) {
+ return isset(JVB_CONTENT[$scope]) && jvbNoBase($scope) === $this->group;
+ }
+
+ if ($parent === 'taxonomy' && defined('JVB_TAXONOMY')) {
+ return isset(JVB_TAXONOMY[$scope]) && jvbNoBase($scope) === $this->group;
+ }
+ }
+
+ // ID-specific match: 'user_123' matches 'user' + 'id'
+ if ($scope === 'id' && str_starts_with($this->group, $parent . '_')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Flush a connected cache group
+ * For ID-specific connections, deletes the specific ID key
+ * For type/overview connections, flushes entire group
+ */
+ private function flushConnection(string $cache_group, array $connection): void
+ {
+ $scope = $connection['scope'] ?? 'id';
+
+ // ID-specific: delete specific key
+ if ($scope === 'id') {
+ $id = $this->extractIdFromGroup();
+
+ if ($id !== null) {
+ self::invalidateKeys($cache_group, $id);
+ return;
+ }
+ }
+
+ // Type/overview: flush entire group
+ self::invalidateAll($cache_group, specific_keys: null, flush_connections: false);
+ }
+
+ /**
+ * Extract ID from group name like 'user_123' -> '123'
+ *
+ * @return string|null
+ */
+ private function extractIdFromGroup(): ?string
+ {
+ if (preg_match('/^[a-z]+_(\d+)$/', $this->group, $matches)) {
+ return $matches[1];
+ }
+
+ return null;
+ }
+
+ /**
+ * Register multiple connections at once
+ */
+ public static function registerConnections(array $connections): void
+ {
+ $existing = self::getConnections();
+ $changed = false;
+
+ foreach ($connections as $cache_group => $configs) {
+ if (!isset($existing[$cache_group])) {
+ $existing[$cache_group] = [];
+ }
+
+ foreach ($configs as $config) {
+ $duplicate = false;
+ foreach ($existing[$cache_group] as $existing_config) {
+ if ($existing_config === $config) {
+ $duplicate = true;
+ break;
+ }
+ }
+
+ if (!$duplicate) {
+ $existing[$cache_group][] = $config;
+ $changed = true;
+ }
+ }
+ }
+
+ if ($changed) {
+ update_option(self::CONNECTIONS_OPTION, $existing, false);
+ self::$connections_cache = $existing;
+ }
+ }
+
+ /**
+ * Handle post save/update
+ */
+ public static function onPostSave(int $post_id, \WP_Post $post): void
+ {
+ // Skip revisions and autosaves
+ if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
+ return;
+ }
+
+ $post_type = jvbNoBase($post->post_type);
+
+ // Invalidate post type cache
+ self::invalidateAll($post_type);
+
+ // Invalidate specific post cache
+ self::invalidateAll($post_id);
+ // Clear WordPress core post object cache
+ clean_post_cache($post_id);
+ }
+
+ /**
+ * Handle post deletion
+ */
+ public static function onPostDelete(int $post_id): void
+ {
+ $post = get_post($post_id);
+ if (!$post) {
+ return;
+ }
+
+ $post_type = jvbNoBase($post->post_type);
+
+ self::invalidateAll($post_type);
+ self::invalidateAll($post_id);
+ // Clear WordPress core post object cache
+ clean_post_cache($post_id);
+ }
+
+ /**
+ * Handle term save/update
+ */
+ public static function onTermSave(int $term_id, int $tt_id, string $taxonomy): void
+ {
+ // Clear WordPress core term cache
+ clean_term_cache($term_id, $taxonomy);
+ $taxonomy = jvbNoBase($taxonomy);
+
+ // Invalidate taxonomy cache
+ self::invalidateAll($taxonomy);
+
+ // Invalidate specific term cache
+ self::invalidateAll($term_id);
+ }
+
+ /**
+ * Handle term deletion
+ */
+ public static function onTermDelete(int $term_id, int $tt_id, string $taxonomy): void
+ {
+ // Clear WordPress core term cache
+ clean_term_cache($term_id, $taxonomy);
+ $taxonomy = jvbNoBase($taxonomy);
+
+ self::invalidateAll($taxonomy);
+ self::invalidateAll($term_id);
+ }
+
+ /**
+ * Handle user update
+ */
+ public static function onUserUpdate(int $user_id, ?\WP_User $old_user_data = null): void
+ {
+ // Invalidate user-specific cache
+ self::invalidateAll($user_id);
+
+ // Invalidate user role caches if roles changed
+ if ($old_user_data) {
+ $user = get_userdata($user_id);
+ if ($user && $user->roles !== $old_user_data->roles) {
+ foreach (array_merge($user->roles, $old_user_data->roles) as $role) {
+ self::invalidateAll($role);
+ }
+ }
+ }
+ // Clear WordPress core user cache
+ clean_user_cache($user_id);
+ }
+
+ /**
+ * Handle user deletion
+ */
+ public static function onUserDelete(int $user_id): void
+ {
+ self::invalidateAll($user_id);
+ // Clear WordPress core user cache
+ clean_user_cache($user_id);
+ }
+
+ /**
+ * Handle post meta updates
+ */
+ public static function onPostMetaUpdate(int $meta_id, int $post_id, string $meta_key, mixed $meta_value): void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $post = get_post($post_id);
+ if (!$post) {
+ return;
+ }
+
+ self::onPostSave($post_id, $post);
+ }
+ public static function onPostMetaDelete(array $meta_ids, int $post_id, string $meta_key, mixed $meta_value):void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $post = get_post($post_id);
+ if (!$post) {
+ return;
+ }
+
+ self::onPostSave($post_id, $post);
+ }
+
+ /**
+ * Handle term meta updates
+ */
+ public static function onTermMetaUpdate(int $meta_id, int $term_id, string $meta_key, mixed $meta_value): void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $term = get_term($term_id);
+ if (!$term || is_wp_error($term)) {
+ return;
+ }
+
+ self::onTermSave($term_id, $term->term_taxonomy_id, $term->taxonomy);
+ }
+
+ public static function onTermMetaDelete(array $meta_ids, int $term_id, string $meta_key, mixed $meta_value):void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $term = get_term($term_id);
+ if (!$term || is_wp_error($term)) {
+ return;
+ }
+
+ self::onTermSave($term_id, $term->term_taxonomy_id, $term->taxonomy);
+ }
+
+ /**
+ * Handle user meta updates
+ */
+ public static function onUserMetaUpdate(int $meta_id, int $user_id, string $meta_key, mixed $meta_value): void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $user = get_userdata($user_id);
+ if (!$user) {
+ return;
+ }
+
+ self::onUserUpdate($user_id, null);
+ }
+
+ public static function onUserMetaDelete(array $meta_ids, int $user_id, string $meta_key, mixed $meta_value):void
+ {
+ if (!str_starts_with($meta_key, BASE)) {
+ return;
+ }
+
+ $user = get_userdata($user_id);
+ if (!$user) {
+ return;
+ }
+
+ self::onUserUpdate($user_id, null);
}
}
--
Gitblit v1.10.0