- 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>
516 lines
15 KiB
PHP
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(); |