ÿØÿà JFIF    ÿÛ „  ( %!1!%*+...983,7(-.- ÿØÿà JFIF    ÿÛ „  ( %!1!%*+...983,7(-.- get_charset_collate(); require_once ABSPATH . 'wp-admin/includes/upgrade.php'; // Providers (sitters/walkers) dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_providers ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED DEFAULT 0, name VARCHAR(150) NOT NULL, email VARCHAR(150) NOT NULL, phone VARCHAR(40), specialty VARCHAR(100), city VARCHAR(100), country VARCHAR(80) DEFAULT 'Mexico', bio TEXT, base_price VARCHAR(60), availability VARCHAR(80), zones TEXT, photo VARCHAR(255), work_images TEXT, id_document VARCHAR(255), certifications TEXT, status VARCHAR(20) DEFAULT 'pending', rating DECIMAL(3,1) DEFAULT 0, review_count INT DEFAULT 0, pets_accepted VARCHAR(100) DEFAULT 'dogs,cats', lang VARCHAR(5) DEFAULT 'es', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Pet owners / users dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_petowners ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED DEFAULT 0, name VARCHAR(150) NOT NULL, email VARCHAR(150) NOT NULL, phone VARCHAR(40), city VARCHAR(100), pet_name VARCHAR(100), pet_type VARCHAR(60), pet_breed VARCHAR(80), pet_age VARCHAR(20), pet_notes TEXT, pet_photo VARCHAR(255), status VARCHAR(20) DEFAULT 'active', lang VARCHAR(5) DEFAULT 'es', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Appointments / calendar dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_appointments ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider_id BIGINT UNSIGNED NOT NULL, owner_id BIGINT UNSIGNED DEFAULT 0, client_name VARCHAR(150) NOT NULL, client_email VARCHAR(150) NOT NULL, client_phone VARCHAR(40), pet_name VARCHAR(100), pet_type VARCHAR(60), service_type VARCHAR(100), appt_date DATE NOT NULL, appt_time TIME NOT NULL, appt_end_time TIME, duration_mins INT DEFAULT 60, location VARCHAR(200), notes TEXT, price DECIMAL(10,2) DEFAULT 0, status VARCHAR(20) DEFAULT 'pending', folio VARCHAR(30), reminder_sent TINYINT DEFAULT 0, lang VARCHAR(5) DEFAULT 'es', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Provider schedule (weekly recurring) dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_schedule ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider_id BIGINT UNSIGNED NOT NULL, day_of_week TINYINT NOT NULL COMMENT '0=Sun,1=Mon...6=Sat', time_start TIME NOT NULL, time_end TIME NOT NULL, slot_mins INT DEFAULT 60, is_active TINYINT DEFAULT 1 ) $c;"); // Blocked dates (provider vacations/unavailable) dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_blocked_dates ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider_id BIGINT UNSIGNED NOT NULL, blocked_date DATE NOT NULL, reason VARCHAR(200), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Services / listings dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_services ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider_id BIGINT UNSIGNED NOT NULL, title VARCHAR(200) NOT NULL, category VARCHAR(80), description TEXT, price DECIMAL(10,2) DEFAULT 0, price_unit VARCHAR(30) DEFAULT 'per night', zones TEXT, availability VARCHAR(80), images TEXT, pets_accepted VARCHAR(100), status VARCHAR(20) DEFAULT 'pending', views INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Reviews dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_reviews ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider_id BIGINT UNSIGNED NOT NULL, appointment_id BIGINT UNSIGNED DEFAULT 0, reviewer_name VARCHAR(150), reviewer_email VARCHAR(150), rating TINYINT DEFAULT 5, comment TEXT, pet_type VARCHAR(60), status VARCHAR(20) DEFAULT 'approved', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Messages / contact dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_messages ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, sender_name VARCHAR(150), sender_email VARCHAR(150), sender_phone VARCHAR(40), receiver_type VARCHAR(20) DEFAULT 'admin', receiver_id BIGINT UNSIGNED DEFAULT 0, subject VARCHAR(200), message TEXT NOT NULL, status VARCHAR(20) DEFAULT 'unread', reply TEXT, lang VARCHAR(5) DEFAULT 'es', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); // Notifications dbDelta("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mpp7_notifications ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, type VARCHAR(50), title VARCHAR(200), message TEXT, is_read TINYINT DEFAULT 0, link VARCHAR(300), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) $c;"); if (!file_exists(MPP7_UPLOAD_DIR)) { wp_mkdir_p(MPP7_UPLOAD_DIR); file_put_contents(MPP7_UPLOAD_DIR . '.htaccess', "Options -Indexes\n"); } // Default options add_option('mpp7_platform_name', 'MyPetPro7.com'); add_option('mpp7_default_lang', 'es'); add_option('mpp7_contact_email', get_option('admin_email')); add_option('mpp7_whatsapp', ''); add_option('mpp7_slot_duration', 60); add_option('mpp7_commission', 10); add_option('mpp7_currency', 'USD'); add_option('mpp7_timezone', 'America/Mexico_City'); add_option('mpp7_auto_approve', '0'); add_option('mpp7_hero_tagline', 'Loving pet care in your neighborhood™'); flush_rewrite_rules(); } /* ══════════════════════════════════════════════ ENQUEUE ASSETS ══════════════════════════════════════════════ */ add_action('wp_enqueue_scripts', 'mpp7_enqueue'); function mpp7_enqueue() { wp_enqueue_style('mpp7-fonts', 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap', [], null); wp_enqueue_style('mpp7-css', MPP7_URL . 'css/style.css', [], MPP7_VER); wp_enqueue_script('mpp7-js', MPP7_URL . 'js/app.js', [], MPP7_VER, true); wp_enqueue_script('mpp7-t', MPP7_URL . 'js/translations.js', [], MPP7_VER, true); wp_localize_script('mpp7-js', 'MPP7', [ 'ajax' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mpp7_nonce'), 'uploadsUrl' => MPP7_UPLOAD_URL, 'lang' => get_option('mpp7_default_lang', 'es'), 'pluginUrl' => MPP7_URL, 'loggedIn' => is_user_logged_in() ? '1' : '0', 'userId' => get_current_user_id(), 'isAdmin' => current_user_can('manage_options') ? '1' : '0', 'loginUrl' => wp_login_url(get_permalink()), 'registerUrl'=> wp_registration_url(), 'currency' => get_option('mpp7_currency', 'USD'), 'slotMins' => (int)get_option('mpp7_slot_duration', 60), 'platform' => get_option('mpp7_platform_name', 'MyPetPro7.com'), 'tagline' => get_option('mpp7_hero_tagline', 'Loving pet care in your neighborhood™'), 'siteUrl' => get_site_url(), ]); } add_action('admin_enqueue_scripts', 'mpp7_admin_enqueue'); function mpp7_admin_enqueue($hook) { if (strpos($hook, 'mpp7') === false) return; wp_enqueue_style('mpp7-admin-css', MPP7_URL . 'css/admin.css', [], MPP7_VER); wp_enqueue_script('mpp7-admin-js', MPP7_URL . 'js/admin.js', ['jquery'], MPP7_VER, true); wp_localize_script('mpp7-admin-js', 'MPP7_ADMIN', [ 'ajax' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mpp7_admin_nonce'), ]); } /* ══════════════════════════════════════════════ SHORTCODES ══════════════════════════════════════════════ */ add_shortcode('mypetpro7', function(){ ob_start(); include MPP7_PATH.'php/platform.php'; return ob_get_clean(); }); add_shortcode('mypetpro7_calendar', function(){ ob_start(); include MPP7_PATH.'php/calendar.php'; return ob_get_clean(); }); add_shortcode('mypetpro7_provider', function(){ ob_start(); include MPP7_PATH.'php/provider-dash.php'; return ob_get_clean(); }); add_shortcode('mypetpro7_user', function(){ ob_start(); include MPP7_PATH.'php/user-dash.php'; return ob_get_clean(); }); /* ══════════════════════════════════════════════ ADMIN MENU ══════════════════════════════════════════════ */ add_action('admin_menu', 'mpp7_admin_menu'); function mpp7_admin_menu() { add_menu_page('MyPetPro7', 'MyPetPro7', 'manage_options', 'mpp7', 'mpp7_pg_overview', 'dashicons-pets', 24); add_submenu_page('mpp7','Vista General','Vista General','manage_options','mpp7','mpp7_pg_overview'); add_submenu_page('mpp7','Proveedores', 'Proveedores', 'manage_options','mpp7-providers', 'mpp7_pg_providers'); add_submenu_page('mpp7','Usuarios', 'Usuarios', 'manage_options','mpp7-users', 'mpp7_pg_users'); add_submenu_page('mpp7','Citas', 'Citas', 'manage_options','mpp7-appointments', 'mpp7_pg_appointments'); add_submenu_page('mpp7','Servicios', 'Servicios', 'manage_options','mpp7-services', 'mpp7_pg_services'); add_submenu_page('mpp7','Mensajes', 'Mensajes', 'manage_options','mpp7-messages', 'mpp7_pg_messages'); add_submenu_page('mpp7','Reseñas', 'Reseñas', 'manage_options','mpp7-reviews', 'mpp7_pg_reviews'); add_submenu_page('mpp7','Ajustes', 'Ajustes', 'manage_options','mpp7-settings', 'mpp7_pg_settings'); } function mpp7_pg_overview() { include MPP7_PATH.'php/admin/overview.php'; } function mpp7_pg_providers() { include MPP7_PATH.'php/admin/providers.php'; } function mpp7_pg_users() { include MPP7_PATH.'php/admin/users.php'; } function mpp7_pg_appointments() { include MPP7_PATH.'php/admin/appointments.php'; } function mpp7_pg_services() { include MPP7_PATH.'php/admin/services.php'; } function mpp7_pg_messages() { include MPP7_PATH.'php/admin/messages.php'; } function mpp7_pg_reviews() { include MPP7_PATH.'php/admin/reviews.php'; } function mpp7_pg_settings() { include MPP7_PATH.'php/admin/settings.php'; } /* ══════════════════════════════════════════════ HELPER FUNCTIONS ══════════════════════════════════════════════ */ function mpp7_check($nonce = 'mpp7_nonce') { check_ajax_referer($nonce, 'nonce'); } function mpp7_folio($id) { return 'MPP7-' . strtoupper(substr(md5($id . time()), 0, 8)); } function mpp7_get_provider_schedule($provider_id, $date) { global $wpdb; $dow = (int)(new DateTime($date))->format('w'); $schedule = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mpp7_schedule WHERE provider_id=%d AND day_of_week=%d AND is_active=1", $provider_id, $dow )); $blocked = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}mpp7_blocked_dates WHERE provider_id=%d AND blocked_date=%s", $provider_id, $date )); if ($blocked || empty($schedule)) return []; $slots = []; $slot_mins = (int)get_option('mpp7_slot_duration', 60); foreach ($schedule as $s) { $cur = new DateTime($date . ' ' . $s->time_start); $end = new DateTime($date . ' ' . $s->time_end); while ($cur < $end) { $t = $cur->format('H:i'); $booked = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}mpp7_appointments WHERE provider_id=%d AND appt_date=%s AND appt_time=%s AND status IN ('pending','confirmed')", $provider_id, $date, $t )); $slots[] = ['time' => $t, 'available' => !$booked]; $cur->modify("+{$slot_mins} minutes"); } } return $slots; } function mpp7_notify_email($to, $subject, $message) { $from = get_option('mpp7_contact_email', get_option('admin_email')); $headers = ['Content-Type: text/plain; charset=UTF-8', 'From: MyPetPro7 <' . $from . '>']; wp_mail($to, $subject, $message, $headers); } /* ══════════════════════════════════════════════ AJAX — GET AVAILABLE SLOTS ══════════════════════════════════════════════ */ foreach (['wp_ajax_nopriv_mpp7_get_slots', 'wp_ajax_mpp7_get_slots'] as $a) add_action($a, function() { mpp7_check(); $pid = intval($_POST['provider_id'] ?? 0); $date = sanitize_text_field($_POST['date'] ?? ''); if (!$pid || !$date) { wp_send_json_error('Params required'); return; } $slots = mpp7_get_provider_schedule($pid, $date); wp_send_json_success(['slots' => $slots, 'date' => $date, 'provider_id' => $pid]); }); /* ══════════════════════════════════════════════ AJAX — BOOK APPOINTMENT ══════════════════════════════════════════════ */ foreach (['wp_ajax_nopriv_mpp7_book', 'wp_ajax_mpp7_book'] as $a) add_action($a, function() { mpp7_check(); global $wpdb; $pid = intval($_POST['provider_id'] ?? 0); $name = sanitize_text_field($_POST['client_name'] ?? ''); $email = sanitize_email($_POST['client_email'] ?? ''); $phone = sanitize_text_field($_POST['client_phone'] ?? ''); $petN = sanitize_text_field($_POST['pet_name'] ?? ''); $petT = sanitize_text_field($_POST['pet_type'] ?? ''); $svc = sanitize_text_field($_POST['service_type'] ?? ''); $date = sanitize_text_field($_POST['appt_date'] ?? ''); $time = sanitize_text_field($_POST['appt_time'] ?? ''); $notes = sanitize_textarea_field($_POST['notes'] ?? ''); $lang = sanitize_text_field($_POST['lang'] ?? 'es'); if (!$pid || !$name || !$email || !$date || !$time) { wp_send_json_error('Required fields missing.'); return; } if (!is_email($email)) { wp_send_json_error('Invalid email.'); return; } // Check double-booking $exists = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$wpdb->prefix}mpp7_appointments WHERE provider_id=%d AND appt_date=%s AND appt_time=%s AND status IN ('pending','confirmed')", $pid, $date, $time )); if ($exists) { wp_send_json_error('That time slot is already booked. Please choose another.'); return; } // Get provider info $prov = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_providers WHERE id=%d", $pid)); // Get owner_id $owner_id = 0; if (is_user_logged_in()) { $ow = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_petowners WHERE user_id=%d", get_current_user_id())); $owner_id = $ow ? $ow->id : 0; } // Calculate price $price = $prov ? floatval(preg_replace('/[^0-9.]/', '', $prov->base_price)) : 0; // Generate folio $folio = mpp7_folio(rand(1000,9999)); $wpdb->insert("{$wpdb->prefix}mpp7_appointments", [ 'provider_id' => $pid, 'owner_id' => $owner_id, 'client_name' => $name, 'client_email' => $email, 'client_phone' => $phone, 'pet_name' => $petN, 'pet_type' => $petT, 'service_type' => $svc, 'appt_date' => $date, 'appt_time' => $time, 'notes' => $notes, 'price' => $price, 'folio' => $folio, 'status' => 'pending', 'lang' => $lang, ]); $appt_id = $wpdb->insert_id; // Email client $msg = "Hola $name,\n\nTu cita fue agendada exitosamente.\n\nFolio: $folio\nFecha: $date\nHora: $time\nServicio: $svc\nMascota: $petN ($petT)\nProveedor: ".($prov->name??'')."\n\nTe contactaremos pronto.\n\n— MyPetPro7.com"; mpp7_notify_email($email, "[MyPetPro7] Cita confirmada — $folio", $msg); // Email provider if ($prov && $prov->email) mpp7_notify_email($prov->email, "[MyPetPro7] Nueva cita: $name — $date $time", "Nueva cita de $name\nMascota: $petN ($petT)\nFecha: $date Hora: $time\nServicio: $svc\nFolio: $folio"); // Notify admin $admin_email = get_option('mpp7_contact_email', get_option('admin_email')); mpp7_notify_email($admin_email, "[MyPetPro7] Nueva cita agendada #$appt_id", "Cliente: $name | Proveedor: ".($prov->name??"N/A")." | Fecha: $date $time | Folio: $folio"); wp_send_json_success(['folio' => $folio, 'appt_id' => $appt_id, 'date' => $date, 'time' => $time, 'provider' => $prov->name ?? '']); }); /* ══════════════════════════════════════════════ AJAX — GET PROVIDERS ══════════════════════════════════════════════ */ foreach (['wp_ajax_nopriv_mpp7_get_providers', 'wp_ajax_mpp7_get_providers'] as $a) add_action($a, function() { mpp7_check(); global $wpdb; $svc = sanitize_text_field($_POST['service'] ?? ''); $city = sanitize_text_field($_POST['city'] ?? ''); $q = sanitize_text_field($_POST['search'] ?? ''); $sql = "SELECT * FROM {$wpdb->prefix}mpp7_providers WHERE status='active'"; if ($svc) $sql .= $wpdb->prepare(' AND specialty LIKE %s', '%'.$svc.'%'); if ($city) $sql .= $wpdb->prepare(' AND city LIKE %s', '%'.$city.'%'); if ($q) $sql .= $wpdb->prepare(' AND (name LIKE %s OR bio LIKE %s)', '%'.$q.'%', '%'.$q.'%'); $rows = $wpdb->get_results($sql . ' ORDER BY rating DESC LIMIT 50'); foreach ($rows as &$r) $r->photo_url = $r->photo ? MPP7_UPLOAD_URL.$r->photo : ''; wp_send_json_success($rows); }); /* ══════════════════════════════════════════════ AJAX — REGISTER PROVIDER ══════════════════════════════════════════════ */ foreach (['wp_ajax_nopriv_mpp7_register_provider', 'wp_ajax_mpp7_register_provider'] as $a) add_action($a, function() { mpp7_check(); global $wpdb; $data = [ 'name' => sanitize_text_field($_POST['name'] ?? ''), 'email' => sanitize_email($_POST['email'] ?? ''), 'phone' => sanitize_text_field($_POST['phone'] ?? ''), 'specialty' => sanitize_text_field($_POST['specialty'] ?? ''), 'city' => sanitize_text_field($_POST['city'] ?? ''), 'bio' => sanitize_textarea_field($_POST['bio'] ?? ''), 'base_price' => sanitize_text_field($_POST['base_price'] ?? ''), 'availability'=> sanitize_text_field($_POST['availability']?? ''), 'lang' => sanitize_text_field($_POST['lang'] ?? 'es'), 'status' => get_option('mpp7_auto_approve','0')==='1' ? 'active' : 'pending', 'user_id' => get_current_user_id(), ]; if (!$data['name'] || !$data['email'] || !$data['specialty']) { wp_send_json_error('Required fields missing.'); return; } // Handle photo if (!empty($_FILES['photo']['name'])) { $allowed = ['image/jpeg','image/png','image/webp']; if (in_array($_FILES['photo']['type'], $allowed) && $_FILES['photo']['size'] <= 4*1024*1024) { if (!file_exists(MPP7_UPLOAD_DIR)) wp_mkdir_p(MPP7_UPLOAD_DIR); $ext = pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION); $fn = 'prov_'.time().'_'.rand(100,999).'.'.$ext; if (move_uploaded_file($_FILES['photo']['tmp_name'], MPP7_UPLOAD_DIR.$fn)) $data['photo'] = $fn; } } $wpdb->insert("{$wpdb->prefix}mpp7_providers", $data); $id = $wpdb->insert_id; mpp7_notify_email($data['email'], "[MyPetPro7] Registro recibido", "Hola {$data['name']},\n\nTu registro fue recibido. Te avisaremos en 24-48h.\n\n— MyPetPro7"); $admin = get_option('mpp7_contact_email', get_option('admin_email')); mpp7_notify_email($admin, "[MyPetPro7] Nuevo proveedor: {$data['name']}", "Revisar en WP Admin → MyPetPro7 → Proveedores\nID: $id\nEmail: {$data['email']}"); wp_send_json_success(['id' => $id]); }); /* ══════════════════════════════════════════════ AJAX — REGISTER PET OWNER ══════════════════════════════════════════════ */ foreach (['wp_ajax_nopriv_mpp7_register_owner', 'wp_ajax_mpp7_register_owner'] as $a) add_action($a, function() { mpp7_check(); global $wpdb; $data = [ 'name' => sanitize_text_field($_POST['name'] ?? ''), 'email' => sanitize_email($_POST['email'] ?? ''), 'phone' => sanitize_text_field($_POST['phone'] ?? ''), 'city' => sanitize_text_field($_POST['city'] ?? ''), 'pet_name' => sanitize_text_field($_POST['pet_name'] ?? ''), 'pet_type' => sanitize_text_field($_POST['pet_type'] ?? ''), 'pet_breed' => sanitize_text_field($_POST['pet_breed'] ?? ''), 'pet_age' => sanitize_text_field($_POST['pet_age'] ?? ''), 'pet_notes' => sanitize_textarea_field($_POST['pet_notes'] ?? ''), 'lang' => sanitize_text_field($_POST['lang'] ?? 'es'), 'status' => 'active', 'user_id' => get_current_user_id(), ]; if (!$data['name'] || !$data['email']) { wp_send_json_error('Required fields.'); return; } $wpdb->insert("{$wpdb->prefix}mpp7_petowners", $data); $id = $wpdb->insert_id; mpp7_notify_email($data['email'], "[MyPetPro7] ¡Bienvenido!", "Hola {$data['name']},\n\nTu cuenta fue creada. Ya puedes buscar cuidadores y agendar citas.\n\n— MyPetPro7"); wp_send_json_success(['id' => $id]); }); /* ══════════════════════════════════════════════ AJAX — GET USER APPOINTMENTS ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_my_appointments', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $uid = get_current_user_id(); $owner = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_petowners WHERE user_id=%d", $uid)); if (!$owner) { wp_send_json_success([]); return; } $appts = $wpdb->get_results($wpdb->prepare( "SELECT a.*, p.name as provider_name, p.specialty FROM {$wpdb->prefix}mpp7_appointments a LEFT JOIN {$wpdb->prefix}mpp7_providers p ON a.provider_id=p.id WHERE a.owner_id=%d ORDER BY a.appt_date DESC, a.appt_time DESC LIMIT 50", $owner->id )); wp_send_json_success($appts); }); /* ══════════════════════════════════════════════ AJAX — GET PROVIDER APPOINTMENTS ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_provider_appointments', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $uid = get_current_user_id(); $prov = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_providers WHERE user_id=%d", $uid)); if (!$prov) { wp_send_json_success([]); return; } $filter = sanitize_text_field($_POST['filter'] ?? 'upcoming'); $where = $filter === 'past' ? 'appt_date < CURDATE()' : 'appt_date >= CURDATE()'; $appts = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mpp7_appointments WHERE provider_id=%d AND $where ORDER BY appt_date, appt_time LIMIT 100", $prov->id )); wp_send_json_success($appts); }); /* ══════════════════════════════════════════════ AJAX — UPDATE APPOINTMENT STATUS ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_update_appt', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $id = intval($_POST['appt_id']); $status = sanitize_text_field($_POST['status']); $allowed = ['confirmed','cancelled','completed','no-show']; if (!in_array($status, $allowed)) { wp_send_json_error('Invalid status.'); return; } $wpdb->update("{$wpdb->prefix}mpp7_appointments", ['status'=>$status], ['id'=>$id], ['%s'], ['%d']); // Notify client $appt = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_appointments WHERE id=%d", $id)); if ($appt) { $msgs = ['confirmed'=>'¡Tu cita fue confirmada!','cancelled'=>'Tu cita fue cancelada.','completed'=>'¡Tu cita fue completada! Gracias.']; if (isset($msgs[$status])) mpp7_notify_email($appt->client_email, "[MyPetPro7] Actualización de cita #{$appt->folio}", "Hola {$appt->client_name},\n\n{$msgs[$status]}\nFecha: {$appt->appt_date} {$appt->appt_time}\nFolio: {$appt->folio}\n\n— MyPetPro7"); } wp_send_json_success(); }); /* ══════════════════════════════════════════════ AJAX — SAVE PROVIDER SCHEDULE ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_save_schedule', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $uid = get_current_user_id(); $prov = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_providers WHERE user_id=%d", $uid)); if (!$prov) { wp_send_json_error('Provider not found.'); return; } $schedule = json_decode(stripslashes($_POST['schedule'] ?? '[]'), true); if (!is_array($schedule)) { wp_send_json_error('Invalid schedule data.'); return; } $wpdb->delete("{$wpdb->prefix}mpp7_schedule", ['provider_id'=>$prov->id], ['%d']); foreach ($schedule as $s) { $start = sanitize_text_field($s['start'] ?? ''); $end = sanitize_text_field($s['end'] ?? ''); if ($start && $end) { $wpdb->insert("{$wpdb->prefix}mpp7_schedule", [ 'provider_id' => $prov->id, 'day_of_week' => intval($s['day'] ?? 0), 'time_start' => $start, 'time_end' => $end, 'slot_mins' => (int)get_option('mpp7_slot_duration', 60), 'is_active' => 1, ], ['%d','%d','%s','%s','%d','%d']); } } wp_send_json_success('Schedule saved.'); }); /* ══════════════════════════════════════════════ AJAX — BLOCK DATE ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_block_date', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $uid = get_current_user_id(); $prov = $wpdb->get_row($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_providers WHERE user_id=%d", $uid)); if (!$prov) { wp_send_json_error(); return; } $date = sanitize_text_field($_POST['date'] ?? ''); $reason = sanitize_text_field($_POST['reason'] ?? ''); if (!$date) { wp_send_json_error('Date required.'); return; } $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$wpdb->prefix}mpp7_blocked_dates WHERE provider_id=%d AND blocked_date=%s", $prov->id, $date)); if ($exists) { $wpdb->delete("{$wpdb->prefix}mpp7_blocked_dates", ['provider_id'=>$prov->id,'blocked_date'=>$date]); wp_send_json_success(['action'=>'unblocked']); } else { $wpdb->insert("{$wpdb->prefix}mpp7_blocked_dates", ['provider_id'=>$prov->id,'blocked_date'=>$date,'reason'=>$reason]); wp_send_json_success(['action'=>'blocked']); } }); /* ══════════════════════════════════════════════ AJAX — GET PROVIDER PROFILE ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_get_my_provider_profile', function() { mpp7_check(); global $wpdb; if (!is_user_logged_in()) { wp_send_json_error(); return; } $uid = get_current_user_id(); $prov = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_providers WHERE user_id=%d", $uid)); if ($prov) { $prov->photo_url = $prov->photo ? MPP7_UPLOAD_URL.$prov->photo : ''; $prov->schedule = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_schedule WHERE provider_id=%d ORDER BY day_of_week,time_start", $prov->id)); $prov->blocked = $wpdb->get_col($wpdb->prepare("SELECT blocked_date FROM {$wpdb->prefix}mpp7_blocked_dates WHERE provider_id=%d", $prov->id)); } wp_send_json_success($prov); }); /* ══════════════════════════════════════════════ AJAX — ADMIN: APPROVE/REJECT PROVIDER ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_admin_provider_status', function() { check_ajax_referer('mpp7_admin_nonce','nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(); return; } global $wpdb; $id = intval($_POST['id']); $status = sanitize_text_field($_POST['status']); if (!in_array($status,['active','pending','inactive','rejected'])) { wp_send_json_error(); return; } $wpdb->update("{$wpdb->prefix}mpp7_providers",['status'=>$status],['id'=>$id],['%s'],['%d']); $prov = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_providers WHERE id=%d",$id)); if ($prov && $prov->email) { $msgs=['active'=>'¡Tu perfil fue APROBADO! Ya apareces en la plataforma.','rejected'=>'Tu perfil fue revisado con estado: rechazado.']; if (isset($msgs[$status])) mpp7_notify_email($prov->email,'[MyPetPro7] Estado de perfil',"Hola {$prov->name},\n\n{$msgs[$status]}\n\n— MyPetPro7"); } wp_send_json_success(); }); /* ══════════════════════════════════════════════ AJAX — ADMIN: ALL APPOINTMENTS ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_admin_appointments', function() { check_ajax_referer('mpp7_admin_nonce','nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(); return; } global $wpdb; $rows = $wpdb->get_results( "SELECT a.*, p.name as prov_name, p.specialty FROM {$wpdb->prefix}mpp7_appointments a LEFT JOIN {$wpdb->prefix}mpp7_providers p ON a.provider_id=p.id ORDER BY a.appt_date DESC, a.appt_time DESC LIMIT 300" ); wp_send_json_success($rows); }); /* ══════════════════════════════════════════════ AJAX — SAVE SETTINGS ══════════════════════════════════════════════ */ add_action('wp_ajax_mpp7_save_settings', function() { check_ajax_referer('mpp7_admin_nonce','nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(); return; } update_option('mpp7_platform_name', sanitize_text_field($_POST['platform_name']??'MyPetPro7.com')); update_option('mpp7_default_lang', sanitize_text_field($_POST['default_lang']??'es')); update_option('mpp7_contact_email', sanitize_email($_POST['contact_email']??'')); update_option('mpp7_whatsapp', sanitize_text_field($_POST['whatsapp']??'')); update_option('mpp7_slot_duration', intval($_POST['slot_duration']??60)); update_option('mpp7_commission', intval($_POST['commission']??10)); update_option('mpp7_currency', sanitize_text_field($_POST['currency']??'USD')); update_option('mpp7_auto_approve', sanitize_text_field($_POST['auto_approve']??'0')); update_option('mpp7_hero_tagline', sanitize_text_field($_POST['hero_tagline']??'Loving pet care in your neighborhood™')); wp_send_json_success(); }); /* ══════════════════════════════════════════════ REST API ══════════════════════════════════════════════ */ add_action('rest_api_init', function() { register_rest_route('mpp7/v1', '/providers', [ 'methods' => 'GET', 'callback'=> function($req) { global $wpdb; $rows = $wpdb->get_results("SELECT id,name,specialty,city,base_price,rating,review_count,photo FROM {$wpdb->prefix}mpp7_providers WHERE status='active' ORDER BY rating DESC LIMIT 100"); foreach ($rows as &$r) $r->photo_url = $r->photo ? MPP7_UPLOAD_URL.$r->photo : ''; return rest_ensure_response($rows); }, 'permission_callback' => '__return_true', ]); register_rest_route('mpp7/v1', '/appointments/(?P\d+)', [ 'methods' => 'GET', 'callback'=> function($req) { global $wpdb; $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}mpp7_appointments WHERE id=%d", $req['id'])); return rest_ensure_response($row); }, 'permission_callback' => function(){ return current_user_can('manage_options'); }, ]); });