Files
care-book-block-ultimate/BACKUP-ESSENTIALS/PRODUCTION-READY/care-booking-block-ultimate/includes/class-cache-manager.php
Emanuel Almeida 38bb926742 chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:34 +01:00

516 lines
15 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Cache manager for Care Booking Block plugin
*
* @package CareBookingBlock
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Cache manager class
*/
class Care_Booking_Cache_Manager
{
/**
* Cache key for blocked doctors
*/
const DOCTORS_CACHE_KEY = 'care_booking_doctors_blocked';
/**
* Cache key prefix for blocked services
*/
const SERVICES_CACHE_PREFIX = 'care_booking_services_blocked_';
/**
* Cache key for restrictions hash
*/
const HASH_CACHE_KEY = 'care_booking_restrictions_hash';
/**
* Default cache expiration (1 hour)
*/
const DEFAULT_EXPIRATION = 3600;
/**
* Smart TTL cache expiration (15 minutes for high-frequency data)
*/
const SMART_TTL_EXPIRATION = 900;
/**
* Long-term cache expiration (4 hours for stable data)
*/
const LONG_TERM_EXPIRATION = 14400;
/**
* Cache blocked doctors
*
* @param array $doctor_ids Array of blocked doctor IDs
* @param int $expiration Cache expiration in seconds
* @return bool True on success, false on failure
*/
public function set_blocked_doctors($doctor_ids, $expiration = null)
{
if ($expiration === null) {
$expiration = $this->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();