From 0113d2e9c9ff34a6ffb10707cc76d34b67a0c367 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 19 Jan 2026 16:29:41 +0000
Subject: [PATCH] =Refactored window.getTemplate into a full templating class window.jvbTemplates. Refactored CRUD.js, UploadManager.js, FormController.js, PopulateForm.js with that in mind
---
inc/managers/CacheManager.php | 743 +++++++++++++++++++++++++++++++++++---------------------
1 files changed, 466 insertions(+), 277 deletions(-)
diff --git a/inc/managers/CacheManager.php b/inc/managers/CacheManager.php
index c8a38b9..2583431 100644
--- a/inc/managers/CacheManager.php
+++ b/inc/managers/CacheManager.php
@@ -13,14 +13,15 @@
*/
class CacheManager
{
+ 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 array $relationships = []; // Type => [related types]
- private static bool $relationships_loaded = false;
+ private static ?CacheManager $singleton = null;
/**
* Private constructor - use for() factory method instead
@@ -33,6 +34,76 @@
if (is_null(static::$use_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);
}
/**
@@ -127,21 +198,21 @@
}
/**
- * Invalidate cache for a content type with automatic cascade
+ * Invalidate cache for a content type
*
* @param string $type Content type to invalidate
- * @param mixed $context Post/Term object or array with relationship data (for cascade)
* @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, $context = null, $specific_keys = null): 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 (don't flush whole group)
+ // If specific keys provided, only delete those
if ($specific_keys !== null) {
$instance = self::for($type);
if (is_array($specific_keys)) {
@@ -152,21 +223,20 @@
$instance->delete($specific_keys);
}
} else {
- // No specific keys - flush the entire group
+ // Flush the entire group
if (function_exists('wp_cache_flush_group')) {
wp_cache_flush_group($type);
} else {
- // Fallback for older WP
wp_cache_flush();
}
}
- // Cascade to related types if context provided
- if ($context !== null) {
- self::cascadeInvalidation($type, $context);
+ // Flush connected caches
+ if ($flush_connections) {
+ self::for($type)->connections();
}
- do_action('jvb_cache_invalidated', $type, $context);
+ do_action('jvb_cache_invalidated', $type);
}
/**
@@ -192,15 +262,14 @@
/**
* Fluent instance method to invalidate this cache type
- * Allows chaining: CacheManager::for('tattoo')->invalidate()->clear()
*
- * @param mixed $context Optional context for cascade
* @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($context = null, $specific_keys = null): self
+ public function invalidate($specific_keys = null, bool $flush_connections = true): self
{
- self::invalidateAll($this->group, $context, $specific_keys);
+ self::invalidateAll($this->group, $specific_keys, $flush_connections);
return $this;
}
@@ -237,7 +306,14 @@
$key = $this->normalizeKey($key);
$cache_key = $this->buildKey($key);
- return wp_cache_get($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 $value;
}
/**
@@ -255,12 +331,18 @@
$key = $this->normalizeKey($key);
$cache_key = $this->buildKey($key);
- // Update timestamp when setting new data
self::updateTimestamp($this->group);
- return wp_cache_set($cache_key, $value, $group, $ttl);
- }
+ // 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)
@@ -273,9 +355,17 @@
$key = $this->normalizeKey($key);
$cache_key = $this->buildKey($key);
- return wp_cache_delete($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;
}
+
/**
* Clear all cache for this group
* @return bool
@@ -285,16 +375,40 @@
try {
if (function_exists('wp_cache_flush_group')) {
wp_cache_flush_group($this->group);
- self::updateTimestamp($this->group);
- return true;
}
- return false;
+
+ // 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;
}
}
/**
+ * Clear all transients for this cache group
+ */
+ private function clearGroupTransients(): void
+ {
+ global $wpdb;
+
+ $pattern = '_transient_' . $this->group . '_' . $this->prefix . '%';
+ $timeout_pattern = '_transient_timeout_' . $this->group . '_' . $this->prefix . '%';
+
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s",
+ $pattern,
+ $timeout_pattern
+ )
+ );
+ }
+
+ /**
* Helper to generateKey from array if applicable
* @param string|array $key
* @return string
@@ -360,308 +474,383 @@
return $this->group;
}
- // ===== RELATIONSHIP MANAGEMENT =====
+ /***************************************************************************
+ * 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 = ??
+ ***************************************************************************/
/**
- * Register cache relationship
- * When $type is invalidated, these related types are also invalidated
+ * Define a connection between cache groups
+ * Connected caches will have their ID-based keys deleted when this cache invalidates
*
- * @param string $type Primary type
- * @param array $config Relationship configuration
- * - 'author' => bool - Invalidate user content caches
- * - 'taxonomies' => array - List of taxonomy types to invalidate
- * - 'content_types' => array - List of content types to invalidate
- * - 'related' => array - Generic related types to invalidate
- * - 'cascade' => callable - Custom cascade function
+ * @param string $type Grand overview ('post', 'taxonomy', 'user')
+ * @param string $scope Type-specific constant, user role, or 'id'
+ * @return self For chaining
*/
- public static function registerRelationship(string $type, array $config): void
+ public function connectTo(string $type, string $scope = 'id'): self
{
- $type = jvbNoBase($type);
+ //TODO: Handle connect to where $type === 'all'
+ $connections = self::getConnections();
- // Merge with existing relationships
- self::$relationships[$type] = array_merge(
- self::$relationships[$type] ?? [],
- $config
- );
+ if (!isset($connections[$this->group])) {
+ $connections[$this->group] = [];
+ }
- // Build reverse relationships for bidirectional linking
- self::buildReverseRelationships($type, $config);
- }
+ $new_connection = [
+ 'parent' => $type,
+ 'scope' => $scope
+ ];
- /**
- * Build reverse relationships (if A relates to B, B should know about A)
- *
- * @param string $type The type being registered
- * @param array $config Its relationship config
- */
- private static function buildReverseRelationships(string $type, array $config): void
- {
- // If this type relates to taxonomies, those taxonomies should know about this type
- if (!empty($config['taxonomies'])) {
- foreach ($config['taxonomies'] as $taxonomy) {
- $taxonomy = jvbNoBase($taxonomy);
- self::$relationships[$taxonomy]['content_types'] =
- array_unique(array_merge(
- self::$relationships[$taxonomy]['content_types'] ?? [],
- [$type]
- ));
+ // Check if already exists
+ foreach ($connections[$this->group] as $existing) {
+ if ($existing === $new_connection) {
+ return $this;
}
}
- // If this type relates to content_types, those types should know about this taxonomy
- if (!empty($config['content_types'])) {
- foreach ($config['content_types'] as $content_type) {
- $content_type = jvbNoBase($content_type);
- self::$relationships[$content_type]['related'] =
- array_unique(array_merge(
- self::$relationships[$content_type]['related'] ?? [],
- [$type]
- ));
+ $connections[$this->group][] = $new_connection;
+ update_option(self::CONNECTIONS_OPTION, $connections, false);
+ self::$connections_cache = $connections;
+
+ return $this;
+ }
+
+ /**
+ * 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;
+ }
}
/**
- * Load relationships from JVB_CONTENT and JVB_TAXONOMY
+ * Handle post save/update
*/
- private static function loadRelationships(): void
+ public static function onPostSave(int $post_id, \WP_Post $post): void
{
- if (self::$relationships_loaded) {
+ // Skip revisions and autosaves
+ if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
- // Load post type relationships
- if (defined('JVB_CONTENT')) {
- foreach (JVB_CONTENT as $slug => $config) {
- $relationships = [];
+ $post_type = jvbNoBase($post->post_type);
- // Author relationship
- if (!($config['no_author'] ?? false)) {
- $relationships['author'] = true;
- }
+ // Invalidate post type cache
+ self::invalidateAll($post_type);
- // Taxonomy relationships
- if (!empty($config['taxonomies'])) {
- $relationships['taxonomies'] = array_map('jvbNoBase', $config['taxonomies']);
- }
-
- // Custom relationships from config
- if (!empty($config['cache_relationships'])) {
- $relationships = array_merge($relationships, $config['cache_relationships']);
- }
-
- if (!empty($relationships)) {
- self::registerRelationship($slug, $relationships);
- }
- }
- }
-
- // Load taxonomy relationships
- if (defined('JVB_TAXONOMY')) {
- foreach (JVB_TAXONOMY as $slug => $config) {
- $relationships = [];
-
- // Content type relationships
- if (!empty($config['for_content'])) {
- $relationships['content_types'] = array_map('jvbNoBase', $config['for_content']);
- }
-
- // Always include generic 'terms' cache
- $relationships['related'] = ['terms'];
-
- // Custom relationships from config
- if (!empty($config['cache_relationships'])) {
- $relationships = array_merge($relationships, $config['cache_relationships']);
- }
-
- if (!empty($relationships)) {
- self::registerRelationship($slug, $relationships);
- }
- }
- }
-
- self::$relationships_loaded = true;
-
- do_action('jvb_cache_relationships_loaded', self::$relationships);
+ // Invalidate specific post cache
+ self::invalidateAll($post_id);
+ // Clear WordPress core post object cache
+ clean_post_cache($post_id);
}
/**
- * Get relationships for a type (for debugging)
- *
- * @param string|null $type Specific type or null for all
- * @return array Relationships
+ * Handle post deletion
*/
- public static function getRelationships(?string $type = null): array
+ public static function onPostDelete(int $post_id): void
{
- self::loadRelationships();
-
- if ($type !== null) {
- return self::$relationships[jvbNoBase($type)] ?? [];
- }
-
- return self::$relationships;
- }
-
- /**
- * Cascade invalidation to related types based on relationships
- *
- * @param string $type Primary type being invalidated
- * @param mixed $context Context with relationship data
- */
- /**
- * Cascade invalidation to related types based on relationships
- */
- private static function cascadeInvalidation(string $type, $context): void
- {
- self::loadRelationships();
-
- $relationships = self::$relationships[$type] ?? [];
- if (empty($relationships)) {
+ $post = get_post($post_id);
+ if (!$post) {
return;
}
- $data = self::extractContext($context);
+ $post_type = jvbNoBase($post->post_type);
- // Author relationship - SIMPLIFIED
- if (!empty($relationships['author'])) {
- $user_ids = self::extractUserIds($data, $relationships['author']);
-
- foreach ($user_ids as $user_id) {
- // Single clean call - handles content, profile, everything
- self::invalidateAll("user_{$user_id}");
- }
- }
-
- // Taxonomy relationships
- if (!empty($relationships['taxonomies']) && !empty($data['ID'])) {
- foreach ($relationships['taxonomies'] as $taxonomy) {
- $taxonomy_full = jvbCheckBase($taxonomy);
- $terms = wp_get_post_terms($data['ID'], $taxonomy_full, ['fields' => 'ids']);
-
- if (!empty($terms) && !is_wp_error($terms)) {
- self::updateTimestamp($taxonomy);
- wp_cache_flush_group($taxonomy);
- }
- }
- }
-
- // Content type relationships (for taxonomies)
- if (!empty($relationships['content_types'])) {
- foreach ($relationships['content_types'] as $content_type) {
- self::updateTimestamp($content_type);
- wp_cache_flush_group($content_type);
- }
- }
-
- // Generic related caches
- if (!empty($relationships['related'])) {
- foreach ($relationships['related'] as $related_type) {
- self::updateTimestamp($related_type);
- wp_cache_flush_group($related_type);
- }
- }
-
- // Custom cascade function
- if (!empty($relationships['cascade']) && is_callable($relationships['cascade'])) {
- call_user_func($relationships['cascade'], $type, $data);
- }
+ self::invalidateAll($post_type);
+ self::invalidateAll($post_id);
+ // Clear WordPress core post object cache
+ clean_post_cache($post_id);
}
/**
- * Extract user IDs from context based on relationship config
- * Supports multiple authors, contributors, etc.
- *
- * @param array $data Context data
- * @param mixed $config Author relationship config (bool or array)
- * @return array User IDs to invalidate
+ * Handle term save/update
*/
- private static function extractUserIds(array $data, $config): array
+ public static function onTermSave(int $term_id, int $tt_id, string $taxonomy): void
{
- $user_ids = [];
+ // Clear WordPress core term cache
+ clean_term_cache($term_id, $taxonomy);
+ $taxonomy = jvbNoBase($taxonomy);
- // Simple case: 'author' => true
- if ($config === true) {
- if (!empty($data['post_author'])) {
- $user_ids[] = $data['post_author'];
- }
- return array_filter($user_ids);
- }
+ // Invalidate taxonomy cache
+ self::invalidateAll($taxonomy);
- // Advanced case: 'author' => ['post_author', 'contributors', 'linked_user']
- if (is_array($config)) {
- foreach ($config as $field) {
- // Handle meta fields
- if (str_starts_with($field, 'meta:') && !empty($data['ID'])) {
- $meta_key = substr($field, 5);
- $value = get_post_meta($data['ID'], BASE . $meta_key, true);
-
- if (is_array($value)) {
- $user_ids = array_merge($user_ids, $value);
- } elseif ($value) {
- $user_ids[] = $value;
- }
- }
- // Handle direct data fields
- elseif (!empty($data[$field])) {
- if (is_array($data[$field])) {
- $user_ids = array_merge($user_ids, $data[$field]);
- } else {
- $user_ids[] = $data[$field];
- }
- }
- }
- }
-
- // Callable: 'author' => function($data) { return [...user_ids]; }
- if (is_callable($config)) {
- $result = call_user_func($config, $data);
- if (is_array($result)) {
- $user_ids = array_merge($user_ids, $result);
- } elseif ($result) {
- $user_ids[] = $result;
- }
- }
-
- return array_unique(array_filter(array_map('intval', $user_ids)));
+ // Invalidate specific term cache
+ self::invalidateAll($term_id);
}
/**
- * Extract context data from various formats
- * Converts WP objects to arrays with relevant data
- *
- * @param mixed $context Post/Term object, array, or ID
- * @return array Normalized context data
+ * Handle term deletion
*/
- private static function extractContext($context): array
+ public static function onTermDelete(int $term_id, int $tt_id, string $taxonomy): void
{
- if (is_array($context)) {
- return $context;
- }
+ // Clear WordPress core term cache
+ clean_term_cache($term_id, $taxonomy);
+ $taxonomy = jvbNoBase($taxonomy);
- if ($context instanceof \WP_Post) {
- return [
- 'ID' => $context->ID,
- 'post_author' => $context->post_author,
- 'post_type' => $context->post_type,
- 'post_status' => $context->post_status,
- ];
- }
+ self::invalidateAll($taxonomy);
+ self::invalidateAll($term_id);
+ }
- if ($context instanceof \WP_Term) {
- return [
- 'term_id' => $context->term_id,
- 'taxonomy' => $context->taxonomy,
- 'parent' => $context->parent,
- ];
- }
+ /**
+ * 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);
- if (is_numeric($context)) {
- $post = get_post($context);
- if ($post) {
- return self::extractContext($post);
+ // 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);
+ }
- return [];
+ /**
+ * 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