Files
care-book-block-ultimate/BACKUP-ESSENTIALS/PRODUCTION-READY/care-booking-block-ultimate/includes/class-kivicare-integration.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

798 lines
27 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* KiviCare integration for Care Booking Block plugin
*
* @package CareBookingBlock
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* KiviCare integration class
*/
class Care_Booking_KiviCare_Integration
{
/**
* Database handler instance
*
* @var Care_Booking_Database_Handler
*/
private $db_handler;
/**
* Restriction model instance
*
* @var Care_Booking_Restriction_Model
*/
private $restriction_model;
/**
* Cache manager instance
*
* @var Care_Booking_Cache_Manager
*/
private $cache_manager;
/**
* Constructor
*
* @param Care_Booking_Database_Handler $db_handler Database handler instance
*/
public function __construct($db_handler)
{
$this->db_handler = $db_handler;
$this->restriction_model = new Care_Booking_Restriction_Model();
$this->cache_manager = new Care_Booking_Cache_Manager();
$this->init_hooks();
}
/**
* Initialize WordPress hooks
*/
private function init_hooks()
{
// Enhanced KiviCare filter hooks with multiple compatibility points
// Priority 10 for standard filtering, Priority 5 for early filtering
add_filter('kc_get_doctors_for_booking', [$this, 'filter_doctors'], 10, 1);
add_filter('kivicare_doctors_list', [$this, 'filter_doctors'], 10, 1);
add_filter('kivicare_get_doctors', [$this, 'filter_doctors'], 10, 1);
// Service filtering with multiple hook points
add_filter('kc_get_services_by_doctor', [$this, 'filter_services'], 10, 2);
add_filter('kivicare_services_list', [$this, 'filter_services'], 10, 2);
add_filter('kivicare_get_services', [$this, 'filter_services'], 10, 2);
// Enhanced CSS injection with optimized priority
add_action('wp_head', [$this, 'inject_restriction_css'], 15);
// Frontend JavaScript for graceful degradation
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_scripts'], 10);
// Frontend CSS for base styles
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_styles'], 10);
// KiviCare 3.0+ REST API hooks
add_filter('rest_pre_serve_request', [$this, 'filter_rest_api_response'], 10, 4);
// Admin bar integration (optional)
if (is_admin_bar_showing()) {
add_action('admin_bar_menu', [$this, 'add_admin_bar_menu'], 100);
}
}
/**
* Filter KiviCare doctors list to remove blocked doctors
*
* @param array $doctors Array of doctors from KiviCare
* @return array Filtered array of doctors
*/
public function filter_doctors($doctors)
{
// Validate input
if (!is_array($doctors)) {
return $doctors;
}
// Skip filtering in admin area (keep full access for administrators)
if (is_admin() && current_user_can('manage_options')) {
return $doctors;
}
try {
// Get blocked doctors (with caching)
$blocked_doctors = $this->restriction_model->get_blocked_doctors();
if (empty($blocked_doctors)) {
return $doctors;
}
// Filter out blocked doctors
$filtered_doctors = [];
foreach ($doctors as $key => $doctor) {
// Handle both array and object formats
$doctor_id = is_array($doctor) ? ($doctor['id'] ?? 0) : ($doctor->id ?? 0);
if (!in_array((int) $doctor_id, $blocked_doctors)) {
$filtered_doctors[$key] = $doctor;
}
}
return $filtered_doctors;
} catch (Exception $e) {
// Log error and return original array on failure
if (function_exists('error_log')) {
error_log('Care Booking Block: Doctor filtering error - ' . $e->getMessage());
}
return $doctors;
}
}
/**
* Filter KiviCare services list to remove blocked services for specific doctor
*
* @param array $services Array of services from KiviCare
* @param int $doctor_id Doctor ID
* @return array Filtered array of services
*/
public function filter_services($services, $doctor_id = null)
{
// Validate input
if (!is_array($services)) {
return $services;
}
// Skip filtering in admin area (keep full access for administrators)
if (is_admin() && current_user_can('manage_options')) {
return $services;
}
try {
$filtered_services = [];
// If no doctor_id provided, try to extract from services or context
if (!$doctor_id) {
$doctor_id = $this->extract_doctor_id_from_context($services);
}
// Get blocked services for this doctor (with enhanced caching)
$blocked_services = $doctor_id ?
$this->restriction_model->get_blocked_services($doctor_id) : [];
// Also get globally blocked doctors to filter services
$blocked_doctors = $this->restriction_model->get_blocked_doctors();
foreach ($services as $key => $service) {
// Handle both array and object formats
$service_id = is_array($service) ? ($service['id'] ?? 0) : ($service->id ?? 0);
$service_doctor_id = is_array($service) ?
($service['doctor_id'] ?? $doctor_id) :
($service->doctor_id ?? $doctor_id);
// Skip if service belongs to a blocked doctor
if ($service_doctor_id && in_array((int) $service_doctor_id, $blocked_doctors)) {
continue;
}
// Skip if service is specifically blocked for this doctor
if ($service_doctor_id && !empty($blocked_services) &&
in_array((int) $service_id, $blocked_services)) {
continue;
}
$filtered_services[$key] = $service;
}
return $filtered_services;
} catch (Exception $e) {
// Log error and return original array on failure
if (function_exists('error_log')) {
error_log('Care Booking Block: Service filtering error - ' . $e->getMessage());
}
return $services;
}
}
/**
* Extract doctor ID from service context or URL parameters
*
* @param array $services Services array
* @return int|null Doctor ID if found
*/
private function extract_doctor_id_from_context($services)
{
// Try to get from first service
if (!empty($services)) {
$first_service = reset($services);
$doctor_id = is_array($first_service) ?
($first_service['doctor_id'] ?? null) :
($first_service->doctor_id ?? null);
if ($doctor_id) {
return (int) $doctor_id;
}
}
// Try to get from URL parameters
if (isset($_GET['doctor_id'])) {
return (int) $_GET['doctor_id'];
}
// Try to get from POST data
if (isset($_POST['doctor_id'])) {
return (int) $_POST['doctor_id'];
}
return null;
}
/**
* Enqueue frontend JavaScript for graceful degradation
*/
public function enqueue_frontend_scripts()
{
// Only on frontend and if KiviCare is active
if (is_admin() || !$this->is_kivicare_active()) {
return;
}
// Check if we're on a page that might have KiviCare content
if (!$this->should_load_frontend_scripts()) {
return;
}
wp_enqueue_script(
'care-booking-frontend',
CARE_BOOKING_BLOCK_PLUGIN_URL . 'public/js/frontend.js',
['jquery'],
CARE_BOOKING_BLOCK_VERSION,
true
);
// Localize script with configuration
wp_localize_script('care-booking-frontend', 'careBookingConfig', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('care_booking_frontend'),
'debug' => defined('WP_DEBUG') && WP_DEBUG,
'fallbackEnabled' => true,
'retryAttempts' => 3,
'retryDelay' => 1000,
'selectors' => [
'doctors' => '.kivicare-doctor, .kc-doctor-item, .doctor-card',
'services' => '.kivicare-service, .kc-service-item, .service-card',
'forms' => '.kivicare-booking-form, .kc-booking-form',
'loading' => '.care-booking-loading'
]
]);
}
/**
* Check if frontend scripts should be loaded on current page
*
* @return bool True if scripts should be loaded
*/
private function should_load_frontend_scripts()
{
global $post;
// Always load on pages with KiviCare shortcodes
if ($post && has_shortcode($post->post_content, 'kivicare')) {
return true;
}
// Load on pages with KiviCare blocks
if ($post && has_block('kivicare/booking', $post->post_content)) {
return true;
}
// Load on template pages that might contain KiviCare
$template = get_page_template_slug();
if (in_array($template, ['page-booking.php', 'page-appointment.php'])) {
return true;
}
// Load if URL contains KiviCare parameters
if (isset($_GET['kivicare']) || isset($_GET['booking']) || isset($_GET['appointment'])) {
return true;
}
return false;
}
/**
* Enqueue frontend CSS for base styles
*/
public function enqueue_frontend_styles()
{
// Only on frontend and if KiviCare is active
if (is_admin() || !$this->is_kivicare_active()) {
return;
}
// Check if we're on a page that might have KiviCare content
if (!$this->should_load_frontend_scripts()) {
return;
}
wp_enqueue_style(
'care-booking-frontend',
CARE_BOOKING_BLOCK_PLUGIN_URL . 'public/css/frontend.css',
[],
CARE_BOOKING_BLOCK_VERSION,
'all'
);
}
/**
* Inject optimized CSS to hide blocked elements on frontend
*
* Priority 15 - After theme styles but before most plugins
*/
public function inject_restriction_css()
{
// Only inject on frontend
if (is_admin()) {
return;
}
// Skip if not on pages with KiviCare content (performance optimization)
if (!$this->should_inject_css()) {
return;
}
try {
// Get blocked doctors and services with caching
$blocked_doctors = $this->restriction_model->get_blocked_doctors();
$blocked_services = $this->get_all_blocked_services();
// Early return if no restrictions
if (empty($blocked_doctors) && empty($blocked_services)) {
return;
}
// Generate optimized CSS
$css = $this->generate_restriction_css($blocked_doctors, $blocked_services);
if (!empty($css)) {
// Output with proper caching headers and minification
echo "\n<!-- Care Booking Block Styles -->\n";
echo '<style id="care-booking-restrictions" data-care-booking="restriction-css" data-version="' . CARE_BOOKING_BLOCK_VERSION . '">';
// Add performance optimizations
if (defined('WP_DEBUG') && WP_DEBUG) {
echo "\n" . $css . "\n";
} else {
// Minified output for production
echo $this->minify_css($css);
}
echo '</style>';
echo "\n<!-- End Care Booking Block Styles -->\n";
}
} catch (Exception $e) {
// Silently fail to avoid breaking frontend
if (function_exists('error_log')) {
error_log('Care Booking Block: CSS injection error - ' . $e->getMessage());
}
// In debug mode, show a minimal error indicator
if (defined('WP_DEBUG') && WP_DEBUG) {
echo '<!-- Care Booking Block: CSS injection failed -->';
}
}
}
/**
* Determine if CSS should be injected on current page
*
* @return bool True if CSS should be injected
*/
private function should_inject_css()
{
// Always inject if KiviCare is active and we have restrictions
if (!$this->is_kivicare_active()) {
return false;
}
// Use the same logic as frontend scripts
return $this->should_load_frontend_scripts();
}
/**
* Minify CSS for production
*
* @param string $css CSS to minify
* @return string Minified CSS
*/
private function minify_css($css)
{
// Remove comments
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
// Remove whitespace
$css = str_replace(["\r\n", "\r", "\n", "\t"], '', $css);
// Remove extra spaces
$css = preg_replace('/\s+/', ' ', $css);
// Remove spaces around specific characters
$css = str_replace(['; ', ' {', '{ ', ' }', '} ', ': ', ', ', ' ,'], [';', '{', '{', '}', '}', ':', ',', ','], $css);
return trim($css);
}
/**
* Generate optimized CSS for hiding blocked elements
*
* @param array $blocked_doctors Array of blocked doctor IDs
* @param array $blocked_services Array of blocked service data
* @return string Generated CSS with optimization and caching
*/
private function generate_restriction_css($blocked_doctors, $blocked_services)
{
// Check cache first
$cache_key = 'care_booking_css_' . md5(serialize([$blocked_doctors, $blocked_services]));
$cached_css = get_transient($cache_key);
if ($cached_css !== false) {
return $cached_css;
}
$css_rules = [];
$css_comments = [];
// CSS for blocked doctors with enhanced selectors
if (!empty($blocked_doctors)) {
$doctor_selectors = [];
$css_comments[] = "/* Blocked doctors: " . count($blocked_doctors) . " */";
foreach ($blocked_doctors as $doctor_id) {
$doctor_id = (int) $doctor_id;
// KiviCare 3.0+ primary selectors
$doctor_selectors[] = ".kivicare-doctor[data-doctor-id=\"{$doctor_id}\"]";
$doctor_selectors[] = ".kc-doctor-item[data-id=\"{$doctor_id}\"]";
$doctor_selectors[] = ".doctor-card[data-doctor=\"{$doctor_id}\"]";
// Legacy selectors
$doctor_selectors[] = "#doctor-{$doctor_id}";
$doctor_selectors[] = ".kc-doctor-{$doctor_id}";
// Form selectors
$doctor_selectors[] = ".doctor-selection option[value=\"{$doctor_id}\"]";
$doctor_selectors[] = "select[name='doctor_id'] option[value=\"{$doctor_id}\"]";
// Booking form selectors
$doctor_selectors[] = ".booking-doctor-{$doctor_id}";
$doctor_selectors[] = ".appointment-doctor-{$doctor_id}";
}
if (!empty($doctor_selectors)) {
// Split into chunks for better CSS performance
$chunks = array_chunk($doctor_selectors, 50);
foreach ($chunks as $chunk) {
$css_rules[] = implode(',', $chunk) . ' { display: none !important; visibility: hidden !important; }';
}
}
}
// CSS for blocked services with enhanced context
if (!empty($blocked_services)) {
$service_selectors = [];
$css_comments[] = "/* Blocked services: " . count($blocked_services) . " */";
foreach ($blocked_services as $service_data) {
$service_id = (int) $service_data['service_id'];
$doctor_id = (int) $service_data['doctor_id'];
// KiviCare 3.0+ primary selectors
$service_selectors[] = ".kivicare-service[data-service-id=\"{$service_id}\"][data-doctor-id=\"{$doctor_id}\"]";
$service_selectors[] = ".kc-service-item[data-service=\"{$service_id}\"][data-doctor=\"{$doctor_id}\"]";
$service_selectors[] = ".service-card[data-service=\"{$service_id}\"][data-doctor=\"{$doctor_id}\"]";
// Legacy selectors
$service_selectors[] = "#service-{$service_id}-doctor-{$doctor_id}";
$service_selectors[] = ".kc-service-{$service_id}.kc-doctor-{$doctor_id}";
// Form selectors
$service_selectors[] = ".service-selection[data-doctor=\"{$doctor_id}\"] option[value=\"{$service_id}\"]";
$service_selectors[] = "select[name='service_id'][data-doctor=\"{$doctor_id}\"] option[value=\"{$service_id}\"]";
// Booking form selectors
$service_selectors[] = ".booking-service-{$service_id}.doctor-{$doctor_id}";
$service_selectors[] = ".appointment-service-{$service_id}.doctor-{$doctor_id}";
}
if (!empty($service_selectors)) {
// Split into chunks for better CSS performance
$chunks = array_chunk($service_selectors, 50);
foreach ($chunks as $chunk) {
$css_rules[] = implode(',', $chunk) . ' { display: none !important; visibility: hidden !important; }';
}
}
}
// Add graceful degradation styles
$css_rules[] = '.care-booking-fallback { opacity: 0.7; pointer-events: none; }';
$css_rules[] = '.care-booking-loading::after { content: "Loading..."; }';
// Combine CSS with optimization
$final_css = '';
if (!empty($css_comments)) {
$final_css .= implode(PHP_EOL, $css_comments) . PHP_EOL;
}
if (!empty($css_rules)) {
// Minify CSS in production
if (defined('WP_DEBUG') && !WP_DEBUG) {
$final_css .= implode('', $css_rules);
} else {
$final_css .= implode(PHP_EOL, $css_rules);
}
}
// Cache for 1 hour
set_transient($cache_key, $final_css, 3600);
return $final_css;
}
/**
* Get all blocked services across all doctors
*
* @return array Array of blocked service data
*/
private function get_all_blocked_services()
{
$blocked_services = [];
// Get all service restrictions
$service_restrictions = $this->restriction_model->get_by_type('service');
foreach ($service_restrictions as $restriction) {
if ($restriction->is_blocked) {
$blocked_services[] = [
'service_id' => (int) $restriction->target_id,
'doctor_id' => (int) $restriction->doctor_id
];
}
}
return $blocked_services;
}
/**
* Add admin bar menu for quick access
*
* @param WP_Admin_Bar $wp_admin_bar WordPress admin bar object
*/
public function add_admin_bar_menu($wp_admin_bar)
{
// Only show for users with manage_options capability
if (!current_user_can('manage_options')) {
return;
}
$wp_admin_bar->add_menu([
'id' => 'care-booking-control',
'title' => __('Care Booking', 'care-booking-block'),
'href' => admin_url('tools.php?page=care-booking-control'),
'meta' => [
'title' => __('Care Booking Control', 'care-booking-block')
]
]);
// Add submenu with statistics
$stats = $this->restriction_model->get_statistics();
$wp_admin_bar->add_menu([
'parent' => 'care-booking-control',
'id' => 'care-booking-stats',
'title' => sprintf(
__('Restrictions: %d doctors, %d services', 'care-booking-block'),
$stats['blocked_doctors'],
$stats['service_restrictions']
),
'href' => admin_url('tools.php?page=care-booking-control'),
]);
}
/**
* Check if specific doctor is blocked
*
* @param int $doctor_id Doctor ID
* @return bool True if blocked, false otherwise
*/
public function is_doctor_blocked($doctor_id)
{
return $this->restriction_model->is_doctor_blocked($doctor_id);
}
/**
* Check if specific service is blocked for a doctor
*
* @param int $service_id Service ID
* @param int $doctor_id Doctor ID
* @return bool True if blocked, false otherwise
*/
public function is_service_blocked($service_id, $doctor_id)
{
return $this->restriction_model->is_service_blocked($service_id, $doctor_id);
}
/**
* Get blocked doctors count
*
* @return int Number of blocked doctors
*/
public function get_blocked_doctors_count()
{
return count($this->restriction_model->get_blocked_doctors());
}
/**
* Get blocked services count for specific doctor
*
* @param int $doctor_id Doctor ID
* @return int Number of blocked services
*/
public function get_blocked_services_count($doctor_id)
{
return count($this->restriction_model->get_blocked_services($doctor_id));
}
/**
* Apply restrictions to KiviCare query (if supported)
*
* @param string $query SQL query
* @param string $context Query context
* @return string Modified query
*/
public function filter_kivicare_query($query, $context = '')
{
// This would be used if KiviCare provides query filtering hooks
// For now, return original query
return $query;
}
/**
* Handle KiviCare appointment booking validation
*
* @param array $booking_data Booking data
* @return bool|WP_Error True if allowed, WP_Error if blocked
*/
public function validate_booking($booking_data)
{
$doctor_id = $booking_data['doctor_id'] ?? 0;
$service_id = $booking_data['service_id'] ?? 0;
// Check if doctor is blocked
if ($this->is_doctor_blocked($doctor_id)) {
return new WP_Error(
'doctor_blocked',
__('This doctor is not available for booking.', 'care-booking-block')
);
}
// Check if service is blocked for this doctor
if ($service_id && $this->is_service_blocked($service_id, $doctor_id)) {
return new WP_Error(
'service_blocked',
__('This service is not available for this doctor.', 'care-booking-block')
);
}
return true;
}
/**
* Get integration status
*
* @return array Status information
*/
public function get_integration_status()
{
return [
'kivicare_active' => $this->is_kivicare_active(),
'hooks_registered' => [
'doctor_filter' => has_filter('kc_get_doctors_for_booking'),
'service_filter' => has_filter('kc_get_services_by_doctor'),
'css_injection' => has_action('wp_head')
],
'cache_status' => $this->cache_manager->get_cache_stats(),
'restrictions' => $this->restriction_model->get_statistics()
];
}
/**
* Filter KiviCare REST API responses for doctor and service listings
*
* @param mixed $served Whether the request has already been served
* @param WP_HTTP_Response $result The response object
* @param WP_REST_Request $request The request object
* @param WP_REST_Server $server The REST server instance
* @return mixed Original served value
*/
public function filter_rest_api_response($served, $result, $request, $server)
{
// Skip if already served or not a KiviCare endpoint
if ($served || !$this->is_kivicare_rest_endpoint($request)) {
return $served;
}
// Skip filtering in admin area for administrators
if (is_admin() && current_user_can('manage_options')) {
return $served;
}
try {
$data = $result->get_data();
if (is_array($data) && isset($data['data'])) {
$route = $request->get_route();
// Filter doctors endpoint
if (strpos($route, '/doctors') !== false && is_array($data['data'])) {
$data['data'] = $this->filter_doctors($data['data']);
$result->set_data($data);
}
// Filter services endpoint
if (strpos($route, '/services') !== false && is_array($data['data'])) {
$doctor_id = $request->get_param('doctor_id') ?: null;
$data['data'] = $this->filter_services($data['data'], $doctor_id);
$result->set_data($data);
}
}
} catch (Exception $e) {
// Log error but don't break API response
if (function_exists('error_log')) {
error_log('Care Booking Block: REST API filtering error - ' . $e->getMessage());
}
}
return $served;
}
/**
* Check if request is for a KiviCare REST endpoint
*
* @param WP_REST_Request $request The request object
* @return bool True if KiviCare endpoint
*/
private function is_kivicare_rest_endpoint($request)
{
$route = $request->get_route();
return strpos($route, '/kivicare/') !== false ||
strpos($route, '/kc/') !== false;
}
/**
* Check if KiviCare plugin is active
*
* @return bool True if KiviCare is active, false otherwise
*/
private function is_kivicare_active()
{
if (!function_exists('is_plugin_active')) {
include_once(ABSPATH . 'wp-admin/includes/plugin.php');
}
return is_plugin_active('kivicare/kivicare.php') ||
is_plugin_active('kivicare-clinic-management-system/kivicare.php');
}
}