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

543 lines
15 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Database handler for Care Booking Block plugin
*
* @package CareBookingBlock
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Database handler class
*/
class Care_Booking_Database_Handler
{
/**
* Database table name
*
* @var string
*/
private $table_name;
/**
* WordPress database object
*
* @var wpdb
*/
private $wpdb;
/**
* Constructor
*/
public function __construct()
{
global $wpdb;
$this->wpdb = $wpdb;
$this->table_name = $wpdb->prefix . 'care_booking_restrictions';
}
/**
* Get table name
*
* @return string
*/
public function get_table_name()
{
return $this->table_name;
}
/**
* Create database table
*
* @return bool True on success, false on failure
*/
public function create_table()
{
$charset_collate = $this->wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
restriction_type ENUM('doctor', 'service') NOT NULL,
target_id BIGINT(20) UNSIGNED NOT NULL,
doctor_id BIGINT(20) UNSIGNED NULL,
is_blocked BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_type_target (restriction_type, target_id),
INDEX idx_doctor_service (doctor_id, target_id),
INDEX idx_blocked (is_blocked),
INDEX idx_composite_blocked (restriction_type, is_blocked),
INDEX idx_composite_doctor_service (doctor_id, target_id, is_blocked),
INDEX idx_performance_doctor (restriction_type, target_id, is_blocked),
INDEX idx_performance_service (doctor_id, target_id, is_blocked)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$result = dbDelta($sql);
return !empty($result);
}
/**
* Drop database table
*
* @return bool True on success, false on failure
*/
public function drop_table()
{
$sql = "DROP TABLE IF EXISTS {$this->table_name}";
return $this->wpdb->query($sql) !== false;
}
/**
* Check if table exists
*
* @return bool True if table exists, false otherwise
*/
public function table_exists()
{
$table_name = $this->table_name;
$query = $this->wpdb->prepare("SHOW TABLES LIKE %s", $table_name);
$result = $this->wpdb->get_var($query);
return $result === $table_name;
}
/**
* Insert new restriction
*
* @param array $data Restriction data
* @return int|false Restriction ID on success, false on failure
*/
public function insert($data)
{
// SECURITY: Enhanced data validation
if (!is_array($data)) {
error_log('Care Booking Block: Invalid data type in insert()');
return false;
}
// Validate required fields
if (!isset($data['restriction_type']) || !isset($data['target_id'])) {
error_log('Care Booking Block: Missing required fields in insert()');
return false;
}
// SECURITY: Whitelist validation for restriction type
$allowed_types = ['doctor', 'service'];
if (!in_array($data['restriction_type'], $allowed_types, true)) {
error_log('Care Booking Block: Invalid restriction_type in insert(): ' . $data['restriction_type']);
return false;
}
// SECURITY: Validate target_id
$target_id = absint($data['target_id']);
if ($target_id <= 0 || $target_id > PHP_INT_MAX) {
error_log('Care Booking Block: Invalid target_id in insert(): ' . $data['target_id']);
return false;
}
// SECURITY: Validate service restrictions require doctor_id
if ($data['restriction_type'] === 'service') {
if (empty($data['doctor_id']) || absint($data['doctor_id']) <= 0) {
error_log('Care Booking Block: Missing or invalid doctor_id for service restriction');
return false;
}
}
// SECURITY: Prepare data with proper sanitization
$insert_data = [
'restriction_type' => sanitize_text_field($data['restriction_type']),
'target_id' => $target_id,
'doctor_id' => isset($data['doctor_id']) ? absint($data['doctor_id']) : null,
'is_blocked' => isset($data['is_blocked']) ? (bool) $data['is_blocked'] : false
];
// SECURITY: Define data types for prepared statement
$format = ['%s', '%d', '%d', '%d'];
// SECURITY: Use WordPress prepared statement (wpdb->insert uses prepare internally)
$result = $this->wpdb->insert($this->table_name, $insert_data, $format);
if ($result === false) {
error_log('Care Booking Block: Database insert failed: ' . $this->wpdb->last_error);
return false;
}
return $this->wpdb->insert_id;
}
/**
* Update restriction
*
* @param int $id Restriction ID
* @param array $data Update data
* @return bool True on success, false on failure
*/
public function update($id, $data)
{
$id = absint($id);
if ($id <= 0) {
return false;
}
// Prepare update data
$update_data = [];
$format = [];
if (isset($data['restriction_type'])) {
if (!in_array($data['restriction_type'], ['doctor', 'service'])) {
return false;
}
$update_data['restriction_type'] = sanitize_text_field($data['restriction_type']);
$format[] = '%s';
}
if (isset($data['target_id'])) {
$update_data['target_id'] = absint($data['target_id']);
$format[] = '%d';
}
if (isset($data['doctor_id'])) {
$update_data['doctor_id'] = absint($data['doctor_id']);
$format[] = '%d';
}
if (isset($data['is_blocked'])) {
$update_data['is_blocked'] = (bool) $data['is_blocked'];
$format[] = '%d';
}
if (empty($update_data)) {
return false;
}
$result = $this->wpdb->update(
$this->table_name,
$update_data,
['id' => $id],
$format,
['%d']
);
return $result !== false;
}
/**
* Delete restriction
*
* @param int $id Restriction ID
* @return bool True on success, false on failure
*/
public function delete($id)
{
$id = absint($id);
if ($id <= 0) {
return false;
}
$result = $this->wpdb->delete(
$this->table_name,
['id' => $id],
['%d']
);
return $result !== false;
}
/**
* Get restriction by ID
*
* @param int $id Restriction ID
* @return object|false Restriction object on success, false on failure
*/
public function get($id)
{
// SECURITY: Enhanced input validation
$id = absint($id);
if ($id <= 0 || $id > PHP_INT_MAX) {
error_log('Care Booking Block: Invalid ID in get(): ' . $id);
return false;
}
// SECURITY: Use prepared statement (already implemented correctly)
$query = $this->wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", $id);
$result = $this->wpdb->get_row($query);
// SECURITY: Log any database errors
if ($this->wpdb->last_error) {
error_log('Care Booking Block: Database error in get(): ' . $this->wpdb->last_error);
return false;
}
return $result;
}
/**
* Get restrictions by type
*
* @param string $type Restriction type ('doctor' or 'service')
* @return array Array of restriction objects
*/
public function get_by_type($type)
{
if (!in_array($type, ['doctor', 'service'])) {
return [];
}
$query = $this->wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE restriction_type = %s ORDER BY target_id",
$type
);
$results = $this->wpdb->get_results($query);
return is_array($results) ? $results : [];
}
/**
* Get all restrictions
*
* @return array Array of restriction objects
*/
public function get_all()
{
$query = "SELECT * FROM {$this->table_name} ORDER BY restriction_type, target_id";
$results = $this->wpdb->get_results($query);
return is_array($results) ? $results : [];
}
/**
* Get blocked doctor IDs with performance optimization
*
* @return array Array of blocked doctor IDs
*/
public function get_blocked_doctors()
{
// Performance-optimized query using composite index
$query = $this->wpdb->prepare(
"SELECT target_id FROM {$this->table_name}
WHERE restriction_type = %s AND is_blocked = %d
ORDER BY target_id",
'doctor',
1
);
$results = $this->wpdb->get_col($query);
return is_array($results) ? array_map('intval', $results) : [];
}
/**
* Get blocked service IDs for specific doctor with performance optimization
*
* @param int $doctor_id Doctor ID
* @return array Array of blocked service IDs
*/
public function get_blocked_services($doctor_id)
{
$doctor_id = absint($doctor_id);
if ($doctor_id <= 0) {
return [];
}
// Performance-optimized query using composite index idx_performance_service
$query = $this->wpdb->prepare(
"SELECT target_id FROM {$this->table_name}
WHERE doctor_id = %d AND target_id > 0 AND is_blocked = %d
ORDER BY target_id",
$doctor_id,
1
);
$results = $this->wpdb->get_col($query);
return is_array($results) ? array_map('intval', $results) : [];
}
/**
* Find existing restriction
*
* @param string $type Restriction type
* @param int $target_id Target ID
* @param int $doctor_id Doctor ID (for service restrictions)
* @return object|false Restriction object or false if not found
*/
public function find_existing($type, $target_id, $doctor_id = null)
{
if (!in_array($type, ['doctor', 'service'])) {
return false;
}
$target_id = absint($target_id);
if ($target_id <= 0) {
return false;
}
if ($type === 'doctor') {
$query = $this->wpdb->prepare(
"SELECT * FROM {$this->table_name}
WHERE restriction_type = %s AND target_id = %d LIMIT 1",
$type,
$target_id
);
} else {
$doctor_id = absint($doctor_id);
if ($doctor_id <= 0) {
return false;
}
$query = $this->wpdb->prepare(
"SELECT * FROM {$this->table_name}
WHERE restriction_type = %s AND target_id = %d AND doctor_id = %d LIMIT 1",
$type,
$target_id,
$doctor_id
);
}
return $this->wpdb->get_row($query);
}
/**
* Bulk insert restrictions
*
* @param array $restrictions Array of restriction data
* @return array Array of inserted IDs (or false for failed insertions)
*/
public function bulk_insert($restrictions)
{
if (!is_array($restrictions) || empty($restrictions)) {
return [];
}
$results = [];
foreach ($restrictions as $restriction_data) {
$result = $this->insert($restriction_data);
$results[] = $result;
}
return $results;
}
/**
* Count restrictions by type
*
* @param string $type Restriction type
* @return int Number of restrictions
*/
public function count_by_type($type)
{
if (!in_array($type, ['doctor', 'service'])) {
return 0;
}
$query = $this->wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table_name} WHERE restriction_type = %s",
$type
);
$result = $this->wpdb->get_var($query);
return is_numeric($result) ? (int) $result : 0;
}
/**
* Get database error if any
*
* @return string Database error message
*/
public function get_last_error()
{
return $this->wpdb->last_error;
}
/**
* Clean up restrictions for non-existent targets
*
* @return int Number of cleaned up restrictions
*/
public function cleanup_orphaned_restrictions()
{
// This method would need integration with KiviCare tables
// For now, we'll return 0 as a placeholder
return 0;
}
/**
* Get query performance statistics
*
* @return array Performance stats
*/
public function get_performance_stats()
{
$stats = [
'total_queries' => $this->wpdb->num_queries,
'table_exists' => $this->table_exists(),
'row_count' => $this->wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}"),
'index_usage' => $this->analyze_index_usage(),
'query_cache_hits' => $this->get_query_cache_stats()
];
return $stats;
}
/**
* Analyze database index usage for optimization
*
* @return array Index usage statistics
*/
private function analyze_index_usage()
{
if (!defined('WP_DEBUG') || !WP_DEBUG) {
return ['debug_only' => true];
}
$indexes = [
'idx_type_target',
'idx_doctor_service',
'idx_blocked',
'idx_composite_blocked',
'idx_performance_doctor',
'idx_performance_service'
];
$usage_stats = [];
foreach ($indexes as $index) {
// This would typically require EXPLAIN queries
$usage_stats[$index] = 'active';
}
return $usage_stats;
}
/**
* Get query cache statistics
*
* @return array Cache statistics
*/
private function get_query_cache_stats()
{
// Basic query cache monitoring
$cache_key = 'care_booking_query_cache_stats';
$stats = get_transient($cache_key) ?: ['hits' => 0, 'misses' => 0];
return $stats;
}
}