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

751 lines
27 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Admin interface for Care Booking Block plugin
*
* @package CareBookingBlock
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Admin interface class
*/
class Care_Booking_Admin_Interface
{
/**
* Database handler instance
*
* @var Care_Booking_Database_Handler
*/
private $db_handler;
/**
* Restriction model instance
*
* @var Care_Booking_Restriction_Model
*/
private $restriction_model;
/**
* Admin page slug
*/
const ADMIN_PAGE_SLUG = 'care-booking-control';
/**
* 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->init_hooks();
}
/**
* Initialize WordPress hooks
*/
private function init_hooks()
{
// Admin menu
add_action('admin_menu', [$this, 'add_admin_menu']);
// Admin scripts and styles
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
// AJAX handlers
add_action('wp_ajax_care_booking_get_restrictions', [$this, 'ajax_get_restrictions']);
add_action('wp_ajax_care_booking_toggle_restriction', [$this, 'ajax_toggle_restriction']);
add_action('wp_ajax_care_booking_bulk_update', [$this, 'ajax_bulk_update']);
add_action('wp_ajax_care_booking_get_entities', [$this, 'ajax_get_entities']);
}
/**
* Add admin menu
*/
public function add_admin_menu()
{
add_management_page(
__('Care Booking Control', 'care-booking-block'),
__('Care Booking Control', 'care-booking-block'),
'manage_options',
self::ADMIN_PAGE_SLUG,
[$this, 'render_admin_page']
);
}
/**
* Enqueue admin assets
*
* @param string $hook_suffix Current admin page
*/
public function enqueue_admin_assets($hook_suffix)
{
// Only load on our admin page
if (strpos($hook_suffix, self::ADMIN_PAGE_SLUG) === false) {
return;
}
// Enqueue CSS
wp_enqueue_style(
'care-booking-admin',
CARE_BOOKING_BLOCK_PLUGIN_URL . 'admin/css/admin-style.css',
[],
CARE_BOOKING_BLOCK_VERSION
);
// Enqueue JavaScript
wp_enqueue_script(
'care-booking-admin',
CARE_BOOKING_BLOCK_PLUGIN_URL . 'admin/js/admin-script.js',
['jquery'],
CARE_BOOKING_BLOCK_VERSION,
true
);
// Localize script
wp_localize_script('care-booking-admin', 'careBookingAjax', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('care_booking_nonce'),
'strings' => [
'loading' => __('Loading...', 'care-booking-block'),
'error' => __('An error occurred. Please try again.', 'care-booking-block'),
'confirm_bulk' => __('Are you sure you want to update all selected restrictions?', 'care-booking-block'),
'success_update' => __('Restriction updated successfully.', 'care-booking-block'),
'success_bulk' => __('Bulk update completed.', 'care-booking-block')
]
]);
}
/**
* Render admin page
*/
public function render_admin_page()
{
// Check KiviCare availability
if (!$this->is_kivicare_active()) {
$this->render_kivicare_warning();
return;
}
include CARE_BOOKING_BLOCK_PLUGIN_DIR . 'admin/partials/admin-display.php';
}
/**
* AJAX handler: Get restrictions
*/
public function ajax_get_restrictions()
{
// SECURITY: Enhanced CSRF protection with additional request validation
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'care_booking_nonce')) {
wp_send_json_error(['message' => __('Security check failed', 'care-booking-block')]);
wp_die(); // Additional security measure
}
// SECURITY: Check if request is actually via AJAX
if (!wp_doing_ajax()) {
wp_send_json_error(['message' => __('Invalid request method', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced capability check with logging
if (!current_user_can('manage_options')) {
error_log('Care Booking Block: Unauthorized access attempt from user ID: ' . get_current_user_id());
wp_send_json_error(['message' => __('Insufficient permissions', 'care-booking-block')]);
wp_die();
}
// SECURITY: Rate limiting check
if (!$this->check_rate_limit('get_restrictions')) {
wp_send_json_error(['message' => __('Too many requests. Please wait.', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced input sanitization and validation
$restriction_type = sanitize_text_field($_POST['restriction_type'] ?? 'all');
$doctor_id = isset($_POST['doctor_id']) ? absint($_POST['doctor_id']) : null;
// SECURITY: Validate restriction_type against whitelist
$allowed_types = ['all', 'doctor', 'service'];
if (!in_array($restriction_type, $allowed_types, true)) {
error_log('Care Booking Block: Invalid restriction_type attempted: ' . $restriction_type);
wp_send_json_error(['message' => __('Invalid restriction type', 'care-booking-block')]);
wp_die();
}
// SECURITY: Validate doctor_id if provided
if ($doctor_id !== null && $doctor_id <= 0) {
wp_send_json_error(['message' => __('Invalid doctor ID', 'care-booking-block')]);
wp_die();
}
try {
if ($restriction_type === 'all') {
$restrictions = $this->restriction_model->get_all();
} elseif (in_array($restriction_type, ['doctor', 'service'])) {
$restrictions = $this->restriction_model->get_by_type($restriction_type);
// Filter by doctor if specified
if ($restriction_type === 'service' && $doctor_id) {
$restrictions = array_filter($restrictions, function($r) use ($doctor_id) {
return $r->doctor_id == $doctor_id;
});
}
} else {
wp_send_json_error(['message' => __('Invalid parameters', 'care-booking-block')]);
}
// SECURITY: Convert to array format with output escaping
$formatted_restrictions = [];
foreach ($restrictions as $restriction) {
$formatted_restrictions[] = [
'id' => (int) $restriction->id,
'restriction_type' => esc_html($restriction->restriction_type),
'target_id' => (int) $restriction->target_id,
'doctor_id' => $restriction->doctor_id ? (int) $restriction->doctor_id : null,
'is_blocked' => (bool) $restriction->is_blocked,
'created_at' => esc_html($restriction->created_at),
'updated_at' => esc_html($restriction->updated_at)
];
}
wp_send_json_success([
'restrictions' => $formatted_restrictions,
'total' => count($formatted_restrictions)
]);
} catch (Exception $e) {
wp_send_json_error(['message' => __('Database error occurred', 'care-booking-block')]);
}
}
/**
* AJAX handler: Toggle restriction
*/
public function ajax_toggle_restriction()
{
// SECURITY: Enhanced CSRF protection
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'care_booking_nonce')) {
wp_send_json_error(['message' => __('Security check failed', 'care-booking-block')]);
wp_die();
}
// SECURITY: AJAX request validation
if (!wp_doing_ajax()) {
wp_send_json_error(['message' => __('Invalid request method', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced capability check with logging
if (!current_user_can('manage_options')) {
error_log('Care Booking Block: Unauthorized toggle attempt from user ID: ' . get_current_user_id());
wp_send_json_error(['message' => __('Insufficient permissions', 'care-booking-block')]);
wp_die();
}
// SECURITY: Rate limiting
if (!$this->check_rate_limit('toggle_restriction')) {
wp_send_json_error(['message' => __('Too many requests. Please wait.', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced parameter validation and sanitization
$restriction_type = sanitize_text_field($_POST['restriction_type'] ?? '');
$target_id = absint($_POST['target_id'] ?? 0);
$doctor_id = isset($_POST['doctor_id']) ? absint($_POST['doctor_id']) : null;
$is_blocked = isset($_POST['is_blocked']) ? (bool) $_POST['is_blocked'] : true;
// SECURITY: Validate required parameters
if (!$restriction_type || !$target_id) {
error_log('Care Booking Block: Missing parameters in toggle_restriction');
wp_send_json_error(['message' => __('Missing required parameters', 'care-booking-block')]);
wp_die();
}
// SECURITY: Whitelist validation for restriction_type
$allowed_types = ['doctor', 'service'];
if (!in_array($restriction_type, $allowed_types, true)) {
error_log('Care Booking Block: Invalid restriction_type in toggle: ' . $restriction_type);
wp_send_json_error(['message' => __('Invalid restriction type', 'care-booking-block')]);
wp_die();
}
// SECURITY: Validate target_id range
if ($target_id <= 0 || $target_id > PHP_INT_MAX) {
wp_send_json_error(['message' => __('Invalid target ID', 'care-booking-block')]);
wp_die();
}
// SECURITY: Service restriction validation
if ($restriction_type === 'service' && (!$doctor_id || $doctor_id <= 0)) {
wp_send_json_error(['message' => __('Valid doctor_id required for service restrictions', 'care-booking-block')]);
wp_die();
}
try {
// Validate target exists in KiviCare
if (!$this->validate_kivicare_target($restriction_type, $target_id, $doctor_id)) {
wp_send_json_error(['message' => __('Target not found', 'care-booking-block')]);
}
// Toggle restriction
$result = $this->restriction_model->toggle($restriction_type, $target_id, $doctor_id, $is_blocked);
if ($result) {
// Get updated/created restriction
$restriction = $this->restriction_model->find_existing($restriction_type, $target_id, $doctor_id);
if ($restriction) {
wp_send_json_success([
'message' => __('Restriction updated successfully', 'care-booking-block'),
'restriction' => [
'id' => (int) $restriction->id,
'restriction_type' => esc_html($restriction->restriction_type),
'target_id' => (int) $restriction->target_id,
'doctor_id' => $restriction->doctor_id ? (int) $restriction->doctor_id : null,
'is_blocked' => (bool) $restriction->is_blocked,
'updated_at' => esc_html($restriction->updated_at)
]
]);
}
}
wp_send_json_error(['message' => __('Failed to update restriction', 'care-booking-block')]);
} catch (Exception $e) {
wp_send_json_error(['message' => __('Database error', 'care-booking-block')]);
}
}
/**
* AJAX handler: Bulk update
*/
public function ajax_bulk_update()
{
// SECURITY: Enhanced CSRF protection
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'care_booking_nonce')) {
wp_send_json_error(['message' => __('Security check failed', 'care-booking-block')]);
wp_die();
}
// SECURITY: AJAX request validation
if (!wp_doing_ajax()) {
wp_send_json_error(['message' => __('Invalid request method', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced capability check with logging
if (!current_user_can('manage_options')) {
error_log('Care Booking Block: Unauthorized bulk update attempt from user ID: ' . get_current_user_id());
wp_send_json_error(['message' => __('Insufficient permissions', 'care-booking-block')]);
wp_die();
}
// SECURITY: Strict rate limiting for bulk operations
if (!$this->check_rate_limit('bulk_update', 5)) { // More restrictive for bulk operations
wp_send_json_error(['message' => __('Too many bulk requests. Please wait.', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced parameter validation
if (!isset($_POST['restrictions'])) {
error_log('Care Booking Block: Missing restrictions parameter in bulk update');
wp_send_json_error(['message' => __('Missing restrictions parameter', 'care-booking-block')]);
wp_die();
}
$restrictions = $_POST['restrictions'];
// SECURITY: Type validation
if (!is_array($restrictions)) {
error_log('Care Booking Block: Invalid restrictions format in bulk update');
wp_send_json_error(['message' => __('Invalid restrictions format', 'care-booking-block')]);
wp_die();
}
// SECURITY: Strict bulk size limits for security
if (count($restrictions) > 50) { // Reduced from 100 for security
error_log('Care Booking Block: Bulk size limit exceeded: ' . count($restrictions));
wp_send_json_error(['message' => __('Bulk size limit exceeded (max 50)', 'care-booking-block')]);
wp_die();
}
// SECURITY: Validate each restriction item
foreach ($restrictions as $index => $restriction) {
if (!is_array($restriction)) {
error_log('Care Booking Block: Invalid restriction item at index: ' . $index);
wp_send_json_error(['message' => __('Invalid restriction data format', 'care-booking-block')]);
wp_die();
}
// Sanitize each restriction
$restrictions[$index] = [
'restriction_type' => sanitize_text_field($restriction['restriction_type'] ?? ''),
'target_id' => absint($restriction['target_id'] ?? 0),
'doctor_id' => isset($restriction['doctor_id']) ? absint($restriction['doctor_id']) : null,
'is_blocked' => isset($restriction['is_blocked']) ? (bool) $restriction['is_blocked'] : true
];
}
try {
$result = $this->restriction_model->bulk_toggle($restrictions);
if (empty($result['errors'])) {
wp_send_json_success([
'message' => __('Bulk update completed', 'care-booking-block'),
'updated' => $result['updated'],
'errors' => []
]);
} else {
// Partial failure
wp_send_json_error([
'message' => __('Partial failure in bulk update', 'care-booking-block'),
'updated' => $result['updated'],
'errors' => $result['errors']
]);
}
} catch (Exception $e) {
wp_send_json_error(['message' => __('Bulk update failed', 'care-booking-block')]);
}
}
/**
* AJAX handler: Get KiviCare entities
*/
public function ajax_get_entities()
{
// SECURITY: Enhanced CSRF protection
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'care_booking_nonce')) {
wp_send_json_error(['message' => __('Security check failed', 'care-booking-block')]);
wp_die();
}
// SECURITY: AJAX request validation
if (!wp_doing_ajax()) {
wp_send_json_error(['message' => __('Invalid request method', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced capability check with logging
if (!current_user_can('manage_options')) {
error_log('Care Booking Block: Unauthorized entities access from user ID: ' . get_current_user_id());
wp_send_json_error(['message' => __('Insufficient permissions', 'care-booking-block')]);
wp_die();
}
// SECURITY: Rate limiting
if (!$this->check_rate_limit('get_entities')) {
wp_send_json_error(['message' => __('Too many requests. Please wait.', 'care-booking-block')]);
wp_die();
}
// SECURITY: Enhanced input validation
$entity_type = sanitize_text_field($_POST['entity_type'] ?? '');
$doctor_id = isset($_POST['doctor_id']) ? absint($_POST['doctor_id']) : null;
if (!$entity_type) {
error_log('Care Booking Block: Missing entity_type parameter');
wp_send_json_error(['message' => __('Missing entity_type parameter', 'care-booking-block')]);
wp_die();
}
// SECURITY: Whitelist validation for entity_type
$allowed_entity_types = ['doctors', 'services'];
if (!in_array($entity_type, $allowed_entity_types, true)) {
error_log('Care Booking Block: Invalid entity type: ' . $entity_type);
wp_send_json_error(['message' => __('Invalid entity type', 'care-booking-block')]);
wp_die();
}
// SECURITY: Validate doctor_id if provided
if ($doctor_id !== null && $doctor_id <= 0) {
wp_send_json_error(['message' => __('Invalid doctor ID', 'care-booking-block')]);
wp_die();
}
// Check KiviCare availability
if (!$this->is_kivicare_active()) {
wp_send_json_error(['message' => __('KiviCare plugin not available', 'care-booking-block')]);
}
try {
if ($entity_type === 'doctors') {
$entities = $this->get_kivicare_doctors();
} else {
$entities = $this->get_kivicare_services($doctor_id);
}
wp_send_json_success([
'entities' => $entities,
'total' => count($entities)
]);
} catch (Exception $e) {
wp_send_json_error(['message' => __('Database error occurred', 'care-booking-block')]);
}
}
/**
* Check if KiviCare plugin is active
*
* @return bool True if KiviCare is active, false otherwise
*/
private function is_kivicare_active()
{
// Check if KiviCare plugin is 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');
}
/**
* Render KiviCare warning
*/
private function render_kivicare_warning()
{
?>
<div class="wrap">
<h1><?php esc_html_e('Care Booking Control', 'care-booking-block'); ?></h1>
<div class="notice notice-error">
<p>
<?php esc_html_e('KiviCare plugin is required for Care Booking Control to work. Please install and activate KiviCare.', 'care-booking-block'); ?>
</p>
</div>
</div>
<?php
}
/**
* Get KiviCare doctors with restriction status
*
* @return array Array of doctors with restriction status
*/
private function get_kivicare_doctors()
{
global $wpdb;
// Get doctors from KiviCare (mock implementation)
// In real implementation, this would query KiviCare tables
$doctors = [];
// Get blocked doctors for status
$blocked_doctors = $this->restriction_model->get_blocked_doctors();
// SECURITY: Mock doctors for testing with output escaping
for ($i = 1; $i <= 10; $i++) {
$doctors[] = [
'id' => $i,
'name' => esc_html("Dr. Test Doctor $i"),
'email' => esc_html("doctor$i@clinic.com"),
'is_blocked' => in_array($i, $blocked_doctors)
];
}
return $doctors;
}
/**
* Get KiviCare services with restriction status
*
* @param int $doctor_id Optional doctor ID to filter services
* @return array Array of services with restriction status
*/
private function get_kivicare_services($doctor_id = null)
{
global $wpdb;
// Get services from KiviCare (mock implementation)
$services = [];
if ($doctor_id) {
// Get blocked services for this doctor
$blocked_services = $this->restriction_model->get_blocked_services($doctor_id);
// SECURITY: Mock services for testing with output escaping
for ($i = 1; $i <= 5; $i++) {
$services[] = [
'id' => $i,
'name' => esc_html("Service $i"),
'doctor_id' => $doctor_id,
'is_blocked' => in_array($i, $blocked_services)
];
}
} else {
// SECURITY: Return all services with output escaping
for ($i = 1; $i <= 20; $i++) {
$services[] = [
'id' => $i,
'name' => esc_html("Service $i"),
'doctor_id' => (($i - 1) % 10) + 1,
'is_blocked' => false
];
}
}
return $services;
}
/**
* Validate KiviCare target exists
*
* @param string $type Target type
* @param int $target_id Target ID
* @param int $doctor_id Doctor ID (for services)
* @return bool True if target exists, false otherwise
*/
private function validate_kivicare_target($type, $target_id, $doctor_id = null)
{
// SECURITY: Enhanced target validation with logging
if (!in_array($type, ['doctor', 'service'], true)) {
error_log('Care Booking Block: Invalid target type in validation: ' . $type);
return false;
}
if ($target_id <= 0) {
error_log('Care Booking Block: Invalid target_id in validation: ' . $target_id);
return false;
}
// Mock validation - always return true for testing
// In real implementation, this would check KiviCare tables with prepared statements
return true;
}
/**
* SECURITY: Rate limiting mechanism
*
* @param string $action Action being performed
* @param int $max_requests Maximum requests allowed
* @param int $time_window Time window in seconds (default: 60)
* @return bool True if within limits, false if rate limited
*/
private function check_rate_limit($action, $max_requests = 30, $time_window = 60)
{
$user_id = get_current_user_id();
$transient_key = 'care_booking_rate_limit_' . $action . '_' . $user_id;
$requests = get_transient($transient_key);
if ($requests === false) {
// First request in time window
set_transient($transient_key, 1, $time_window);
return true;
}
if ($requests >= $max_requests) {
error_log("Care Booking Block: Rate limit exceeded for action '$action' by user $user_id");
return false;
}
// Increment counter
set_transient($transient_key, $requests + 1, $time_window);
return true;
}
/**
* SECURITY: Sanitize and validate admin page content
*
* @param mixed $data Data to sanitize
* @return mixed Sanitized data
*/
private function sanitize_admin_data($data)
{
if (is_string($data)) {
return sanitize_text_field($data);
}
if (is_array($data)) {
return array_map([$this, 'sanitize_admin_data'], $data);
}
if (is_int($data)) {
return absint($data);
}
if (is_bool($data)) {
return (bool) $data;
}
return $data;
}
/**
* SECURITY: Log security events
*
* @param string $event Event description
* @param array $context Event context
*/
private function log_security_event($event, $context = [])
{
$log_entry = sprintf(
'Care Booking Block Security: %s | User ID: %d | IP: %s | Context: %s',
$event,
get_current_user_id(),
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
json_encode($context)
);
error_log($log_entry);
// Trigger action for external security monitoring
do_action('care_booking_security_event', $event, $context);
}
/**
* SECURITY: Validate WordPress environment
*
* @return bool True if environment is secure
*/
private function validate_environment()
{
// Check if we're in WordPress admin
if (!is_admin()) {
$this->log_security_event('Invalid environment: not admin area');
return false;
}
// Check if user is logged in
if (!is_user_logged_in()) {
$this->log_security_event('Invalid environment: user not logged in');
return false;
}
// Check for multisite restrictions
if (is_multisite() && !is_super_admin()) {
$this->log_security_event('Invalid environment: multisite without super admin');
return false;
}
return true;
}
/**
* SECURITY: Enhanced error handling with security logging
*
* @param string $error_message Error message
* @param array $context Error context
*/
private function handle_security_error($error_message, $context = [])
{
$this->log_security_event('Security Error: ' . $error_message, $context);
// Don't expose sensitive information in error messages
$safe_message = __('A security error occurred. Please try again.', 'care-booking-block');
wp_send_json_error(['message' => $safe_message]);
wp_die();
}
}