300, // 5 minutes - frequently updated 'hidden_entities' => 300, // 5 minutes - critical for performance 'css_complete' => 300, // 5 minutes - UI critical 'entity_search' => 900, // 15 minutes - relatively stable 'statistics' => 600, // 10 minutes - admin dashboard 'audit_summary' => 1800, // 30 minutes - security data 'system_health' => 3600, // 1 hour - system monitoring 'api_responses' => 600 // 10 minutes - API data ]; /** * Constructor * * @param SecurityValidator $security Security validator instance * @since 1.0.0 */ public function __construct(SecurityValidator $security) { $this->security = $security; $this->initializeCacheGroups(); $this->initializeStats(); } /** * Initialize cache groups for organization * * @return void * @since 1.0.0 */ private function initializeCacheGroups(): void { $this->cache_groups = [ 'restrictions' => [ 'restriction_', 'entity_', 'hidden_' ], 'css' => [ 'css_complete', 'css_entity_' ], 'search' => [ 'search_doctors_', 'search_services_', 'search_results_' ], 'statistics' => [ 'stats_', 'dashboard_', 'performance_' ], 'security' => [ 'audit_', 'security_', 'rate_limit_' ] ]; } /** * Initialize cache statistics tracking * * @return void * @since 1.0.0 */ private function initializeStats(): void { $this->cache_stats = [ 'hits' => 0, 'misses' => 0, 'sets' => 0, 'deletes' => 0, 'flushes' => 0 ]; } /** * Get cached value with statistics tracking * * @param string $key Cache key * @param string $group Cache group (optional) * @return mixed Cached value or false if not found * @since 1.0.0 */ public function get(string $key, string $group = ''): mixed { $full_key = $this->buildCacheKey($key, $group); $value = get_transient($full_key); if ($value !== false) { $this->cache_stats['hits']++; // Hook for cache hit monitoring do_action('care_book_ultimate_cache_hit', $key, $group, strlen(serialize($value))); } else { $this->cache_stats['misses']++; // Hook for cache miss monitoring do_action('care_book_ultimate_cache_miss', $key, $group); } return $value; } /** * Set cached value with expiration * * @param string $key Cache key * @param mixed $value Value to cache * @param string $group Cache group (optional) * @param int|null $expiration Expiration time in seconds (optional) * @return bool Success status * @since 1.0.0 */ public function set(string $key, mixed $value, string $group = '', ?int $expiration = null): bool { $full_key = $this->buildCacheKey($key, $group); // Determine expiration time if ($expiration === null) { $expiration = $this->getDefaultExpiration($group); } // Validate cache size (prevent memory issues) $serialized_size = strlen(serialize($value)); if ($serialized_size > 1048576) { // 1MB limit $this->security->logSecurityEvent('cache_size_limit', "Large cache entry: {$key} ({$serialized_size} bytes)"); return false; } $result = set_transient($full_key, $value, $expiration); if ($result) { $this->cache_stats['sets']++; // Hook for cache set monitoring do_action('care_book_ultimate_cache_set', $key, $group, $serialized_size, $expiration); } return $result; } /** * Delete cached value * * @param string $key Cache key * @param string $group Cache group (optional) * @return bool Success status * @since 1.0.0 */ public function delete(string $key, string $group = ''): bool { $full_key = $this->buildCacheKey($key, $group); $result = delete_transient($full_key); if ($result) { $this->cache_stats['deletes']++; // Hook for cache delete monitoring do_action('care_book_ultimate_cache_delete', $key, $group); } return $result; } /** * Flush cache group * * @param string $group Cache group to flush * @return int Number of keys deleted * @since 1.0.0 */ public function flushGroup(string $group): int { if (!isset($this->cache_groups[$group])) { return 0; } $deleted = 0; $patterns = $this->cache_groups[$group]; foreach ($patterns as $pattern) { $deleted += $this->deleteByPattern($this->cache_prefix . $pattern); } if ($deleted > 0) { $this->cache_stats['flushes']++; // Hook for group flush monitoring do_action('care_book_ultimate_cache_flush_group', $group, $deleted); } return $deleted; } /** * Flush all plugin caches * * @return int Number of keys deleted * @since 1.0.0 */ public function flushAll(): int { $deleted = 0; foreach (array_keys($this->cache_groups) as $group) { $deleted += $this->flushGroup($group); } // Also clear any standalone cache entries $deleted += $this->deleteByPattern($this->cache_prefix); if ($deleted > 0) { $this->cache_stats['flushes']++; // Hook for full flush monitoring do_action('care_book_ultimate_cache_flush_all', $deleted); } return $deleted; } /** * Warm cache with frequently accessed data * * @return void * @since 1.0.0 */ public function warmCache(): void { // Warm hidden entities cache for each type foreach (RestrictionType::cases() as $entityType) { $cache_key = 'hidden_' . $entityType->value; if ($this->get($cache_key) === false) { // Cache miss - warm the cache // Note: This would require repository injection in real implementation do_action('care_book_ultimate_warm_cache', $entityType->value); } } // Warm CSS cache if ($this->get('css_complete', 'css') === false) { do_action('care_book_ultimate_warm_css_cache'); } // Warm statistics cache if ($this->get('dashboard_stats', 'statistics') === false) { do_action('care_book_ultimate_warm_stats_cache'); } // Hook for additional cache warming do_action('care_book_ultimate_cache_warmed'); } /** * Get cache statistics * * @return array Cache statistics * @since 1.0.0 */ public function getStatistics(): array { $total_requests = $this->cache_stats['hits'] + $this->cache_stats['misses']; $hit_rate = $total_requests > 0 ? round(($this->cache_stats['hits'] / $total_requests) * 100, 2) : 0; $stats = array_merge($this->cache_stats, [ 'hit_rate' => $hit_rate, 'total_requests' => $total_requests, 'memory_usage' => $this->getMemoryUsage(), 'cache_sizes' => $this->getCacheSizes() ]); return $stats; } /** * Get cache health status * * @return array Health status information * @since 1.0.0 */ public function getHealthStatus(): array { $stats = $this->getStatistics(); $health = [ 'overall_status' => 'healthy', 'hit_rate' => $stats['hit_rate'], 'memory_usage' => $stats['memory_usage'], 'issues' => [] ]; // Check hit rate if ($stats['hit_rate'] < 70) { $health['issues'][] = 'Low cache hit rate'; $health['overall_status'] = 'warning'; } // Check memory usage if ($stats['memory_usage']['percentage'] > 80) { $health['issues'][] = 'High memory usage'; $health['overall_status'] = 'warning'; } // Check for excessive cache misses if ($stats['misses'] > 1000) { $health['issues'][] = 'High cache miss count'; if ($health['overall_status'] === 'healthy') { $health['overall_status'] = 'warning'; } } return $health; } /** * Selective invalidation for entity changes * * @param RestrictionType $entityType Entity type * @param int $entityId Entity ID * @return int Number of keys invalidated * @since 1.0.0 */ public function invalidateEntity(RestrictionType $entityType, int $entityId): int { $invalidated = 0; // Invalidate entity-specific caches $entity_keys = [ 'entity_' . $entityType->value . '_' . $entityId, 'hidden_' . $entityType->value, 'search_' . $entityType->value . '_' . $entityId ]; foreach ($entity_keys as $key) { if ($this->delete($key)) { $invalidated++; } } // Invalidate CSS cache (affects UI) if ($this->delete('css_complete', 'css')) { $invalidated++; } // Invalidate statistics cache if ($this->delete('dashboard_stats', 'statistics')) { $invalidated++; } // Hook for additional invalidation do_action('care_book_ultimate_cache_invalidated', $entityType->value, $entityId, $invalidated); return $invalidated; } /** * Smart cache preloading based on usage patterns * * @param array $usage_data Usage pattern data * @return void * @since 1.0.0 */ public function preloadCache(array $usage_data = []): void { // Preload most accessed entities if (!empty($usage_data['popular_doctors'])) { foreach ($usage_data['popular_doctors'] as $doctorId) { $this->preloadEntityData(RestrictionType::DOCTOR, (int) $doctorId); } } if (!empty($usage_data['popular_services'])) { foreach ($usage_data['popular_services'] as $serviceId) { $this->preloadEntityData(RestrictionType::SERVICE, (int) $serviceId); } } // Preload critical system data $this->warmCache(); // Hook for custom preloading do_action('care_book_ultimate_cache_preloaded', $usage_data); } /** * Build full cache key * * @param string $key Base key * @param string $group Cache group * @return string Full cache key * @since 1.0.0 */ private function buildCacheKey(string $key, string $group = ''): string { $full_key = $this->cache_prefix . $key; if (!empty($group)) { $full_key = $this->cache_prefix . $group . '_' . $key; } // Ensure key length doesn't exceed WordPress limits if (strlen($full_key) > 172) { // WordPress transient key limit is 172 characters $full_key = $this->cache_prefix . md5($full_key); } return $full_key; } /** * Get default expiration time for cache group * * @param string $group Cache group * @return int Expiration time in seconds * @since 1.0.0 */ private function getDefaultExpiration(string $group): int { return self::CACHE_TIMES[$group] ?? self::CACHE_TIMES['restrictions']; } /** * Delete cache entries by pattern * * @param string $pattern Pattern to match * @return int Number of keys deleted * @since 1.0.0 */ private function deleteByPattern(string $pattern): int { global $wpdb; $deleted = 0; // Query database for matching transient keys $sql = $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name LIKE '_transient_%'", '%' . $wpdb->esc_like($pattern) . '%' ); $results = $wpdb->get_col($sql); foreach ($results as $option_name) { // Extract transient name (remove _transient_ prefix) $transient_name = str_replace('_transient_', '', $option_name); if (delete_transient($transient_name)) { $deleted++; } } return $deleted; } /** * Get current memory usage * * @return array Memory usage information * @since 1.0.0 */ private function getMemoryUsage(): array { $current = memory_get_usage(true); $limit = $this->parseMemoryLimit(); return [ 'current' => $current, 'limit' => $limit, 'percentage' => $limit > 0 ? round(($current / $limit) * 100, 2) : 0, 'formatted' => [ 'current' => size_format($current), 'limit' => size_format($limit) ] ]; } /** * Get cache sizes by group * * @return array Cache sizes * @since 1.0.0 */ private function getCacheSizes(): array { global $wpdb; $sizes = []; foreach (array_keys($this->cache_groups) as $group) { $patterns = $this->cache_groups[$group]; $total_size = 0; $count = 0; foreach ($patterns as $pattern) { $full_pattern = $this->cache_prefix . $pattern; $sql = $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name LIKE '_transient_%'", '%' . $wpdb->esc_like($full_pattern) . '%' ); $results = $wpdb->get_col($sql); foreach ($results as $value) { $total_size += strlen($value); $count++; } } $sizes[$group] = [ 'size' => $total_size, 'count' => $count, 'formatted_size' => size_format($total_size) ]; } return $sizes; } /** * Parse memory limit from PHP setting * * @return int Memory limit in bytes * @since 1.0.0 */ private function parseMemoryLimit(): int { $limit = ini_get('memory_limit'); if ($limit == -1) { return 0; // Unlimited } $value = (int) $limit; $unit = strtolower(substr($limit, -1)); switch ($unit) { case 'g': $value *= 1024; // Fallthrough case 'm': $value *= 1024; // Fallthrough case 'k': $value *= 1024; } return $value; } /** * Preload entity-specific data * * @param RestrictionType $entityType Entity type * @param int $entityId Entity ID * @return void * @since 1.0.0 */ private function preloadEntityData(RestrictionType $entityType, int $entityId): void { // This would trigger loading of entity data into cache // Implementation would depend on repository injection do_action('care_book_ultimate_preload_entity', $entityType->value, $entityId); } /** * Initialize cache management hooks * * @return void * @since 1.0.0 */ public function initialize(): void { // Hook into plugin events for automatic cache management add_action('care_book_ultimate_restriction_created', [$this, 'onRestrictionChange'], 10, 2); add_action('care_book_ultimate_restriction_updated', [$this, 'onRestrictionChange'], 10, 2); add_action('care_book_ultimate_restriction_deleted', [$this, 'onRestrictionChange'], 10, 1); // Scheduled cache maintenance add_action('care_book_ultimate_cache_maintenance', [$this, 'performMaintenance']); // Hook for cache warming during quiet periods add_action('care_book_ultimate_warm_cache_scheduled', [$this, 'warmCache']); do_action('care_book_ultimate_cache_manager_initialized'); } /** * Handle restriction changes * * @param int $restriction_id Restriction ID * @param mixed $restriction_data Restriction data (optional) * @return void * @since 1.0.0 */ public function onRestrictionChange(int $restriction_id, mixed $restriction_data = null): void { // Invalidate all caches related to restrictions $this->flushGroup('restrictions'); $this->flushGroup('css'); $this->flushGroup('statistics'); // Hook for additional invalidation do_action('care_book_ultimate_cache_restriction_change', $restriction_id); } /** * Perform cache maintenance tasks * * @return void * @since 1.0.0 */ public function performMaintenance(): void { // Clean up expired transients (WordPress doesn't always do this automatically) wp_cache_flush(); // Log cache statistics $stats = $this->getStatistics(); if ($stats['hit_rate'] < 50) { $this->security->logSecurityEvent('cache_performance', "Low cache hit rate: {$stats['hit_rate']}%"); } // Reset statistics $this->initializeStats(); // Hook for additional maintenance do_action('care_book_ultimate_cache_maintenance_performed', $stats); } }