/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ get_cache_timeout(); } return set_transient(self::DOCTORS_CACHE_KEY, $doctor_ids, $expiration); } /** * Get blocked doctors from cache * * @return array|false Array of doctor IDs or false if not cached */ public function get_blocked_doctors() { return get_transient(self::DOCTORS_CACHE_KEY); } /** * Cache blocked services for specific doctor * * @param int $doctor_id Doctor ID * @param array $service_ids Array of blocked service IDs * @param int $expiration Cache expiration in seconds * @return bool True on success, false on failure */ public function set_blocked_services($doctor_id, $service_ids, $expiration = null) { if ($expiration === null) { $expiration = $this->get_cache_timeout(); } $cache_key = self::SERVICES_CACHE_PREFIX . (int) $doctor_id; return set_transient($cache_key, $service_ids, $expiration); } /** * Get blocked services for specific doctor from cache * * @param int $doctor_id Doctor ID * @return array|false Array of service IDs or false if not cached */ public function get_blocked_services($doctor_id) { $cache_key = self::SERVICES_CACHE_PREFIX . (int) $doctor_id; return get_transient($cache_key); } /** * Set restrictions hash for change detection * * @param string $hash Restrictions hash * @param int $expiration Cache expiration in seconds * @return bool True on success, false on failure */ public function set_restrictions_hash($hash, $expiration = null) { if ($expiration === null) { $expiration = $this->get_cache_timeout(); } return set_transient(self::HASH_CACHE_KEY, $hash, $expiration); } /** * Get restrictions hash from cache * * @return string|false Hash string or false if not cached */ public function get_restrictions_hash() { return get_transient(self::HASH_CACHE_KEY); } /** * Invalidate all plugin caches with smart recovery * * @param bool $smart_recovery Whether to enable smart cache recovery * @return bool True on success */ public function invalidate_all($smart_recovery = true) { $start_time = microtime(true); // Delete main cache keys delete_transient(self::DOCTORS_CACHE_KEY); delete_transient(self::HASH_CACHE_KEY); // Delete all service caches (optimized pattern-based deletion) global $wpdb; $service_prefix = '_transient_' . self::SERVICES_CACHE_PREFIX; $timeout_prefix = '_transient_timeout_' . self::SERVICES_CACHE_PREFIX; // Use optimized queries with LIMIT for large datasets $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 1000", $service_prefix . '%')); $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 1000", $timeout_prefix . '%')); // Clear smart cache stats delete_transient('care_booking_cache_stats'); // Smart recovery - preload critical caches if ($smart_recovery && class_exists('Care_Booking_Database_Handler')) { wp_schedule_single_event(time() + 30, 'care_booking_smart_cache_recovery'); } // Performance tracking $execution_time = (microtime(true) - $start_time) * 1000; if (defined('WP_DEBUG') && WP_DEBUG) { error_log(sprintf('Care Booking Block: Cache invalidation completed in %.2fms', $execution_time)); } // Trigger WordPress action for other plugins/themes do_action('care_booking_cache_cleared', $execution_time); return true; } /** * Invalidate doctor-specific caches * * @param int $doctor_id Doctor ID * @return bool True on success */ public function invalidate_doctor_cache($doctor_id) { // Invalidate blocked doctors cache (affects all doctors) delete_transient(self::DOCTORS_CACHE_KEY); // Invalidate blocked services cache for this doctor $cache_key = self::SERVICES_CACHE_PREFIX . (int) $doctor_id; delete_transient($cache_key); // Invalidate hash cache delete_transient(self::HASH_CACHE_KEY); return true; } /** * Invalidate service-specific caches * * @param int $service_id Service ID * @param int $doctor_id Doctor ID * @return bool True on success */ public function invalidate_service_cache($service_id, $doctor_id) { // Invalidate blocked services cache for this doctor $cache_key = self::SERVICES_CACHE_PREFIX . (int) $doctor_id; delete_transient($cache_key); // Invalidate hash cache delete_transient(self::HASH_CACHE_KEY); return true; } /** * Warm up caches with fresh data * * @param Care_Booking_Database_Handler $db_handler Database handler instance * @return bool True on success */ public function warm_up_cache($db_handler) { try { // Warm up blocked doctors cache $blocked_doctors = $db_handler->get_blocked_doctors(); $this->set_blocked_doctors($blocked_doctors); // Generate and cache restrictions hash $hash = $this->generate_restrictions_hash($db_handler); $this->set_restrictions_hash($hash); return true; } catch (Exception $e) { // Log error if logging is available if (function_exists('error_log')) { error_log('Care Booking Block: Cache warm-up failed - ' . $e->getMessage()); } return false; } } /** * Check if cache needs refresh based on restrictions hash * * @param Care_Booking_Database_Handler $db_handler Database handler instance * @return bool True if cache needs refresh, false otherwise */ public function needs_refresh($db_handler) { $current_hash = $this->get_restrictions_hash(); if ($current_hash === false) { // No cached hash - needs refresh return true; } $actual_hash = $this->generate_restrictions_hash($db_handler); return $current_hash !== $actual_hash; } /** * Generate hash of current restrictions for change detection * * @param Care_Booking_Database_Handler $db_handler Database handler instance * @return string Hash of current restrictions */ public function generate_restrictions_hash($db_handler) { $restrictions = $db_handler->get_all(); // Create a deterministic hash from restrictions data $hash_data = []; foreach ($restrictions as $restriction) { $hash_data[] = sprintf( '%s-%d-%d-%d', $restriction->restriction_type, $restriction->target_id, $restriction->doctor_id ?? 0, $restriction->is_blocked ? 1 : 0 ); } sort($hash_data); // Ensure consistent ordering return md5(implode('|', $hash_data)); } /** * Get cache timeout from WordPress options * * @return int Cache timeout in seconds */ public function get_cache_timeout() { $timeout = get_option('care_booking_cache_timeout', self::DEFAULT_EXPIRATION); // Ensure timeout is within reasonable bounds $timeout = max(300, min(86400, (int) $timeout)); // Between 5 minutes and 24 hours return $timeout; } /** * Set cache timeout in WordPress options * * @param int $timeout Timeout in seconds * @return bool True on success, false on failure */ public function set_cache_timeout($timeout) { $timeout = max(300, min(86400, (int) $timeout)); return update_option('care_booking_cache_timeout', $timeout); } /** * Get cache statistics * * @return array Array of cache statistics */ public function get_cache_stats() { global $wpdb; // Count service cache entries $service_count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_' . self::SERVICES_CACHE_PREFIX . '%' )); return [ 'doctors_cached' => get_transient(self::DOCTORS_CACHE_KEY) !== false, 'service_caches' => (int) $service_count, 'hash_cached' => get_transient(self::HASH_CACHE_KEY) !== false, 'cache_timeout' => $this->get_cache_timeout() ]; } /** * Preload service caches for multiple doctors * * @param array $doctor_ids Array of doctor IDs * @param Care_Booking_Database_Handler $db_handler Database handler instance * @return int Number of caches preloaded */ public function preload_service_caches($doctor_ids, $db_handler) { if (!is_array($doctor_ids) || empty($doctor_ids)) { return 0; } $preloaded = 0; foreach ($doctor_ids as $doctor_id) { // Check if cache already exists if ($this->get_blocked_services($doctor_id) === false) { // Cache miss - preload from database $blocked_services = $db_handler->get_blocked_services($doctor_id); if ($this->set_blocked_services($doctor_id, $blocked_services)) { $preloaded++; } } } return $preloaded; } /** * Clean up expired caches * * @return int Number of expired caches cleaned */ public function cleanup_expired_caches() { global $wpdb; // WordPress automatically handles transient cleanup, but we can force it $cleaned = 0; // Delete expired transients $expired_transients = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_care_booking_%' AND option_value < UNIX_TIMESTAMP()" ); foreach ($expired_transients as $timeout_option) { $transient_name = str_replace('_transient_timeout_', '_transient_', $timeout_option); delete_option($timeout_option); delete_option($transient_name); $cleaned++; } return $cleaned; } /** * Hook into WordPress action for automatic cache invalidation */ public static function init_cache_hooks() { // Invalidate cache when restrictions are modified add_action('care_booking_restriction_updated', [__CLASS__, 'handle_restriction_change'], 10, 3); add_action('care_booking_restriction_created', [__CLASS__, 'handle_restriction_change'], 10, 3); add_action('care_booking_restriction_deleted', [__CLASS__, 'handle_restriction_change'], 10, 3); } /** * Handle restriction changes for cache invalidation * * @param string $type Restriction type * @param int $target_id Target ID * @param int $doctor_id Doctor ID (optional) */ public static function handle_restriction_change($type, $target_id, $doctor_id = null) { $cache_manager = new self(); if ($type === 'doctor') { $cache_manager->invalidate_doctor_cache($target_id); } elseif ($type === 'service' && $doctor_id) { $cache_manager->invalidate_service_cache($target_id, $doctor_id); } } /** * Smart cache with intelligent TTL based on access patterns * * @param string $key Cache key * @param mixed $data Data to cache * @param string $type Cache type ('frequent', 'stable', 'default') * @return bool True on success */ public function smart_cache($key, $data, $type = 'default') { $ttl = $this->get_smart_ttl($type); // Add access tracking for performance analytics $this->track_cache_access($key, 'set'); return set_transient($key, $data, $ttl); } /** * Get smart TTL based on cache type and usage patterns * * @param string $type Cache type * @return int TTL in seconds */ private function get_smart_ttl($type) { switch ($type) { case 'frequent': return self::SMART_TTL_EXPIRATION; case 'stable': return self::LONG_TERM_EXPIRATION; default: return self::DEFAULT_EXPIRATION; } } /** * Track cache access patterns for optimization * * @param string $key Cache key * @param string $action Action type (get/set/hit/miss) * @return void */ private function track_cache_access($key, $action) { if (!defined('WP_DEBUG') || !WP_DEBUG) { return; // Only track in debug mode } $stats_key = 'care_booking_cache_stats'; $stats = get_transient($stats_key) ?: []; $stats[$key][$action] = ($stats[$key][$action] ?? 0) + 1; $stats[$key]['last_accessed'] = time(); set_transient($stats_key, $stats, DAY_IN_SECONDS); } /** * Bulk cache operations for maximum efficiency * * @param array $cache_data Array of [key => data] pairs * @param string $type Cache type * @return array Results of cache operations */ public function bulk_cache($cache_data, $type = 'default') { $results = []; $ttl = $this->get_smart_ttl($type); foreach ($cache_data as $key => $data) { $results[$key] = set_transient($key, $data, $ttl); } return $results; } } // Initialize cache hooks Care_Booking_Cache_Manager::init_cache_hooks();