get_charset_collate(); $sql = []; $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_incidents ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, driver_id BIGINT NOT NULL, trip_id BIGINT NULL, lat DECIMAL(10,7) NOT NULL DEFAULT 0, lng DECIMAL(10,7) NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'active', chunks_count INT NOT NULL DEFAULT 0, incident_at DATETIME NOT NULL, resolved_at DATETIME NULL, notes TEXT NULL, PRIMARY KEY (id), KEY driver_id (driver_id) ) $charset;"; $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_zone_alerts ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(160) NOT NULL DEFAULT '', description TEXT NULL, lat DECIMAL(10,7) NOT NULL DEFAULT 0, lng DECIMAL(10,7) NOT NULL DEFAULT 0, radius_km DECIMAL(5,2) NOT NULL DEFAULT 1.00, bonus_amount DECIMAL(10,2) NOT NULL DEFAULT 0, bonus_currency CHAR(3) NOT NULL DEFAULT 'USD', countdown_seconds INT NOT NULL DEFAULT 180, type VARCHAR(20) NOT NULL DEFAULT 'hotspot', active TINYINT(1) NOT NULL DEFAULT 1, expires_at DATETIME NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id) ) $charset;"; $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_zone_alert_responses ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, zone_id BIGINT NOT NULL, driver_id BIGINT NOT NULL, response VARCHAR(20) NOT NULL DEFAULT '', responded_at DATETIME NOT NULL, PRIMARY KEY (id), KEY zone_driver (zone_id, driver_id) ) $charset;"; $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_scheduled_trips ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, trip_uuid VARCHAR(64) NOT NULL DEFAULT '', driver_id BIGINT NOT NULL, passenger_name VARCHAR(160) NOT NULL DEFAULT '', pickup_time DATETIME NOT NULL, pickup_address VARCHAR(255) NOT NULL DEFAULT '', dropoff_address VARCHAR(255) NOT NULL DEFAULT '', estimated_fare DECIMAL(10,2) NOT NULL DEFAULT 0, currency CHAR(3) NOT NULL DEFAULT 'USD', confirmed TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY driver_id (driver_id) ) $charset;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; foreach ( $sql as $s ) { dbDelta( $s ); } } /* ───────────────────────────────────────────────────────────── ENQUEUE ───────────────────────────────────────────────────────────── */ public function enqueue() { global $post; if ( ! is_a( $post, 'WP_Post' ) || ! has_shortcode( $post->post_content, 'autobooking_driver_dashboard' ) ) { return; } $base = plugin_dir_url( __FILE__ ); $ver = self::VERSION; // Google Maps // Google Maps API key is stored in wp_options (ab_gmaps_key) and set via Admin → Config. // NEVER hardcode the key here — read it from the database at runtime. $gmaps_key = get_option( 'ab_gmaps_key', '' ); // CSS wp_enqueue_style( 'abd-dashboard', $base . 'assets/driver-dashboard.css', [], $ver ); // JS wp_enqueue_script( 'abd-dashboard', $base . 'assets/driver-dashboard.js', [], $ver, true ); // Build driver data from user meta $user = wp_get_current_user(); $uid = $user->ID; $driver_name = get_user_meta( $uid, 'driver_name', true ); if ( ! $driver_name ) { $driver_name = get_user_meta( $uid, 'full_name', true ); } if ( ! $driver_name ) { $fn = get_user_meta( $uid, 'first_name', true ); $ln = get_user_meta( $uid, 'last_name', true ); $driver_name = trim( "$fn $ln" ); } if ( ! $driver_name ) { $driver_name = $user->display_name; } $photo = get_user_meta( $uid, 'photo_url', true ); if ( ! $photo ) { $photo = get_user_meta( $uid, 'driver_photo', true ); } $vehicle_type = get_user_meta( $uid, 'vehicle_type', true ); $vehicle_plate = get_user_meta( $uid, 'vehicle_plate', true ); if ( ! $vehicle_plate ) { $vehicle_plate = get_user_meta( $uid, 'license_plate', true ); } $insurance_expiry = get_user_meta( $uid, 'insurance_expiry', true ); $license_expiry = get_user_meta( $uid, 'license_expiry', true ); $inspection_expiry = get_user_meta( $uid, 'inspection_expiry', true ); wp_localize_script( 'abd-dashboard', 'AB_DRIVER_CFG', [ 'nonce' => wp_create_nonce( 'wp_rest' ), 'api_root' => esc_url_raw( get_rest_url() ), 'user_id' => $uid, 'gmaps_key' => $gmaps_key, 'driver' => [ 'name' => $driver_name, 'photo' => $photo ?: get_avatar_url( $uid, [ 'size' => 112 ] ) ?: '', 'vehicle_type' => $vehicle_type ?: '', 'vehicle_plate' => $vehicle_plate ?: '', 'insurance_expiry' => $insurance_expiry ?: '', 'license_expiry' => $license_expiry ?: '', 'inspection_expiry' => $inspection_expiry ?: '', 'email' => $user->user_email, 'phone' => get_user_meta( $uid, 'phone', true ) ?: get_user_meta( $uid, 'billing_phone', true ) ?: '', ], ] ); } /* ───────────────────────────────────────────────────────────── REST ROUTES ───────────────────────────────────────────────────────────── */ public function register_routes() { $ns = self::NAMESPACE; $auth = [ 'permission_callback' => [ $this, 'is_driver' ] ]; // GET/POST driver status register_rest_route( $ns, '/driver/status', [ [ 'methods' => 'GET', 'callback' => [ $this, 'rest_get_status' ], 'permission_callback' => [ $this, 'is_driver' ], ], [ 'methods' => 'POST', 'callback' => [ $this, 'rest_post_status' ], 'permission_callback' => [ $this, 'is_driver' ], ], ] ); // GET current trip register_rest_route( $ns, '/driver/current-trip', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_current_trip' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET earnings register_rest_route( $ns, '/driver/earnings', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_earnings' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET metrics register_rest_route( $ns, '/driver/metrics', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_metrics' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET history register_rest_route( $ns, '/driver/history', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_history' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET scheduled register_rest_route( $ns, '/driver/scheduled', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_scheduled' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET profile register_rest_route( $ns, '/driver/profile', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_profile' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // GET zones register_rest_route( $ns, '/driver/zones', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_zones' ], 'permission_callback' => '__return_true', ] ); // POST zone-response register_rest_route( $ns, '/driver/zone-response', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_zone_response' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // POST incident/sos register_rest_route( $ns, '/incident/sos', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_sos' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // POST telemetry/position register_rest_route( $ns, '/telemetry/position', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_position' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // POST trip/start register_rest_route( $ns, '/trip/start', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_trip_start' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); // POST trip/finish register_rest_route( $ns, '/trip/finish', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_trip_finish' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); register_rest_route( $ns, '/driver/change-password', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_change_password' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); register_rest_route( $ns, '/driver/update-profile', [ 'methods' => 'POST', 'callback' => [ $this, 'rest_update_profile' ], 'permission_callback' => [ $this, 'is_driver' ], ] ); } /* ───────────────────────────────────────────────────────────── PERMISSION ───────────────────────────────────────────────────────────── */ public function is_driver() { if ( ! is_user_logged_in() ) return new WP_Error( 'not_logged_in', 'Authentication required.', [ 'status' => 401 ] ); $user = wp_get_current_user(); if ( in_array( 'driver', (array) $user->roles, true ) || in_array( 'administrator', (array) $user->roles, true ) ) { return true; } return new WP_Error( 'not_driver', 'Access denied.', [ 'status' => 403 ] ); } /* ───────────────────────────────────────────────────────────── REST HANDLERS ───────────────────────────────────────────────────────────── */ // GET /driver/status public function rest_get_status( $req ) { global $wpdb; $uid = get_current_user_id(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT online, last_seen, current_trip_id FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d", $uid ) ); if ( ! $row ) { return rest_ensure_response( [ 'online' => false, 'last_seen' => null, 'current_trip_id' => null ] ); } return rest_ensure_response( [ 'online' => (bool) $row->online, 'last_seen' => $row->last_seen, 'current_trip_id' => $row->current_trip_id, ] ); } // POST /driver/status public function rest_post_status( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $online = ! empty( $body['online'] ); $lat = isset( $body['lat'] ) ? floatval( $body['lat'] ) : null; $lng = isset( $body['lng'] ) ? floatval( $body['lng'] ) : null; $now = current_time( 'mysql' ); $exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d", $uid ) ); $data = [ 'online' => $online ? 1 : 0, 'last_seen' => $now, ]; if ( $lat !== null ) $data['last_lat'] = $lat; if ( $lng !== null ) $data['last_lng'] = $lng; if ( $exists ) { $wpdb->update( "{$wpdb->prefix}ab_driver_status", $data, [ 'driver_id' => $uid ] ); } else { $data['driver_id'] = $uid; $wpdb->insert( "{$wpdb->prefix}ab_driver_status", $data ); } // Manage sessions if ( $online ) { // Insert new session (go online) $sess = [ 'driver_id' => $uid, 'started_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]; if ( $lat !== null ) { $sess['start_lat'] = $lat; $sess['start_lng'] = $lng; } $wpdb->insert( "{$wpdb->prefix}ab_driver_sessions", $sess ); } else { // Close latest open session $sess_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}ab_driver_sessions WHERE driver_id = %d AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1", $uid ) ); if ( $sess_id ) { $upd = [ 'ended_at' => $now, 'updated_at' => $now ]; if ( $lat !== null ) { $upd['end_lat'] = $lat; $upd['end_lng'] = $lng; } // Calculate online_seconds $start = $wpdb->get_var( $wpdb->prepare( "SELECT started_at FROM {$wpdb->prefix}ab_driver_sessions WHERE id = %d", $sess_id ) ); if ( $start ) { $upd['online_seconds'] = max( 0, strtotime( $now ) - strtotime( $start ) ); } $wpdb->update( "{$wpdb->prefix}ab_driver_sessions", $upd, [ 'id' => $sess_id ] ); } } return rest_ensure_response( [ 'ok' => true, 'online' => $online ] ); } // GET /driver/current-trip public function rest_current_trip( $req ) { global $wpdb; $uid = get_current_user_id(); $statuses = "'assigned','en_route','waiting','in_progress'"; $trip = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status IN ($statuses) ORDER BY updated_at DESC LIMIT 1", $uid ), ARRAY_A ); if ( ! $trip ) { return rest_ensure_response( null ); } // Unread chat count $unread = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}autobooking_chat WHERE ride_id = %d AND sender != 'driver'", $trip['id'] ) ); $trip['unread_count'] = $unread; return rest_ensure_response( $trip ); } // GET /driver/earnings public function rest_earnings( $req ) { global $wpdb; $uid = get_current_user_id(); $period = $req->get_param( 'period' ) ?: 'day'; switch ( $period ) { case 'week': $where_date = "AND DATE(created_at) >= CURDATE() - INTERVAL 6 DAY"; break; case 'month': $where_date = "AND YEAR(created_at)=YEAR(NOW()) AND MONTH(created_at)=MONTH(NOW())"; break; case 'year': $where_date = "AND YEAR(created_at)=YEAR(NOW())"; break; default: // day $where_date = "AND DATE(created_at) = CURDATE()"; break; } $row = $wpdb->get_row( $wpdb->prepare( "SELECT COALESCE(SUM(fare_total_amount),0) AS gross, COALESCE(SUM(platform_fee_amount),0) AS fee, COALESCE(SUM(driver_payout_amount),0) AS net, COUNT(*) AS trips_count, COALESCE(SUM(fare_distance_m),0) AS distance_m FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status = 'finished' $where_date", $uid ) ); // Last 7 days $last7 = $wpdb->get_results( $wpdb->prepare( "SELECT DATE(created_at) AS the_date, COALESCE(SUM(driver_payout_amount),0) AS net FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status = 'finished' AND DATE(created_at) >= CURDATE() - INTERVAL 6 DAY GROUP BY DATE(created_at) ORDER BY DATE(created_at) ASC", $uid ), ARRAY_A ); // Fill missing days $days_map = []; for ( $i = 6; $i >= 0; $i-- ) { $d = date( 'Y-m-d', strtotime( "-$i days" ) ); $days_map[ $d ] = 0; } foreach ( $last7 as $r ) { $days_map[ $r['the_date'] ] = (float) $r['net']; } $last7_arr = []; foreach ( $days_map as $d => $v ) { $last7_arr[] = [ 'date' => $d, 'net' => $v ]; } return rest_ensure_response( [ 'gross' => (float) $row->gross, 'fee' => (float) $row->fee, 'net' => (float) $row->net, 'trips_count' => (int) $row->trips_count, 'distance_km' => round( (float) $row->distance_m / 1000, 2 ), 'last_7_days' => $last7_arr, ] ); } // GET /driver/metrics public function rest_metrics( $req ) { global $wpdb; $uid = get_current_user_id(); // Last 30 days trips assigned to driver $total_assigned = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status != 'available' AND created_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $accepted = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status NOT IN ('assigned','canceled') AND created_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $finished = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status = 'finished' AND created_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $canceled = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status = 'canceled' AND created_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $avg_rating = (float) $wpdb->get_var( $wpdb->prepare( "SELECT AVG(driver_rating) FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND driver_rating > 0 AND created_at >= NOW() - INTERVAL 30 DAY", $uid ) ); // Online hours this week from sessions $online_secs = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COALESCE(SUM(online_seconds),0) FROM {$wpdb->prefix}ab_driver_sessions WHERE driver_id = %d AND started_at >= CURDATE() - INTERVAL 6 DAY", $uid ) ); // Zone responses $zones_responded = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_zone_alert_responses WHERE driver_id = %d AND response = 'going' AND responded_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $zones_ignored = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_zone_alert_responses WHERE driver_id = %d AND response IN ('not_going','ignored') AND responded_at >= NOW() - INTERVAL 30 DAY", $uid ) ); $acceptance_rate = $total_assigned > 0 ? round( ( $accepted / $total_assigned ) * 100, 1 ) : 0; $total_started = $accepted; $completion_rate = $total_started > 0 ? round( ( $finished / $total_started ) * 100, 1 ) : 0; return rest_ensure_response( [ 'acceptance_rate' => $acceptance_rate, 'completion_rate' => $completion_rate, 'avg_rating' => round( $avg_rating, 2 ), 'online_hours_week'=> round( $online_secs / 3600, 1 ), 'zones_responded' => $zones_responded, 'zones_ignored' => $zones_ignored, ] ); } // GET /driver/history public function rest_history( $req ) { global $wpdb; $uid = get_current_user_id(); $page = max( 1, intval( $req->get_param( 'page' ) ?: 1 ) ); $per_page = max( 1, min( 100, intval( $req->get_param( 'per_page' ) ?: 20 ) ) ); $offset = ( $page - 1 ) * $per_page; $date_from = sanitize_text_field( $req->get_param( 'date_from' ) ?: '' ); $date_to = sanitize_text_field( $req->get_param( 'date_to' ) ?: '' ); try { $where = $wpdb->prepare( "WHERE driver_id = %d", $uid ); if ( $date_from ) $where .= $wpdb->prepare( " AND DATE(created_at) >= %s", $date_from ); if ( $date_to ) $where .= $wpdb->prepare( " AND DATE(created_at) <= %s", $date_to ); $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_trips $where" ); if ( $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id, trip_uuid, created_at, passenger_name, pickup_lat, pickup_lng, dropoff_lat, dropoff_lng, fare_total_amount, platform_fee_amount, driver_payout_amount, status, driver_rating, currency FROM {$wpdb->prefix}ab_trips $where ORDER BY created_at DESC LIMIT %d OFFSET %d", $per_page, $offset ), ARRAY_A ); if ( $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); return rest_ensure_response( [ 'ok' => true, 'rows' => $rows ?: [], 'total' => $total, 'page' => $page, 'per_page' => $per_page, 'total_pages'=> $total > 0 ? (int) ceil( $total / $per_page ) : 0, ] ); } catch ( \Exception $e ) { return rest_ensure_response( [ 'ok' => true, 'rows' => [], 'total' => 0, 'page' => $page, 'per_page' => $per_page, 'total_pages'=> 0, ] ); } } // GET /driver/scheduled public function rest_scheduled( $req ) { global $wpdb; $uid = get_current_user_id(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ab_trips WHERE driver_id = %d AND status = 'assigned' AND pickup_time > NOW() ORDER BY pickup_time ASC", $uid ), ARRAY_A ); return rest_ensure_response( $rows ?: [] ); } // GET /driver/profile public function rest_profile( $req ) { $uid = get_current_user_id(); $user = get_userdata( $uid ); $driver_name = get_user_meta( $uid, 'driver_name', true ); if ( ! $driver_name ) $driver_name = get_user_meta( $uid, 'full_name', true ); if ( ! $driver_name ) { $fn = get_user_meta( $uid, 'first_name', true ); $ln = get_user_meta( $uid, 'last_name', true ); $driver_name = trim( "$fn $ln" ); } if ( ! $driver_name ) $driver_name = $user->display_name; $photo = get_user_meta( $uid, 'photo_url', true ) ?: get_user_meta( $uid, 'driver_photo', true ); $vehicle_plate = get_user_meta( $uid, 'vehicle_plate', true ) ?: get_user_meta( $uid, 'license_plate', true ); $insurance_expiry = get_user_meta( $uid, 'insurance_expiry', true ); $license_expiry = get_user_meta( $uid, 'license_expiry', true ); $inspection_expiry = get_user_meta( $uid, 'inspection_expiry', true ); $now = time(); return rest_ensure_response( [ 'name' => $driver_name, 'email' => $user->user_email, 'phone' => get_user_meta( $uid, 'phone', true ) ?: get_user_meta( $uid, 'billing_phone', true ), 'vehicle_type' => get_user_meta( $uid, 'vehicle_type', true ), 'vehicle_plate' => $vehicle_plate, 'photo_url' => $photo, 'insurance_expiry' => $insurance_expiry, 'license_expiry' => $license_expiry, 'inspection_expiry' => $inspection_expiry, 'insurance_status' => $this->doc_status( $insurance_expiry ), 'license_status' => $this->doc_status( $license_expiry ), 'inspection_status' => $this->doc_status( $inspection_expiry ), ] ); } private function doc_status( $expiry_date ) { if ( ! $expiry_date ) return 'unknown'; $ts = strtotime( $expiry_date ); $diff = $ts - time(); if ( $diff < 0 ) return 'expired'; if ( $diff < 30 * 86400 ) return 'warning'; return 'ok'; } // POST /driver/change-password public function rest_change_password( $req ) { $uid = get_current_user_id(); $current = $req->get_param('current_password'); $new_pw = $req->get_param('new_password'); $confirm = $req->get_param('confirm_password'); if ( ! $current || ! $new_pw || ! $confirm ) { return new WP_Error( 'missing_fields', 'Todos los campos son requeridos.', [ 'status' => 400 ] ); } if ( $new_pw !== $confirm ) { return new WP_Error( 'password_mismatch', 'Las contraseñas no coinciden.', [ 'status' => 400 ] ); } if ( strlen( $new_pw ) < 8 ) { return new WP_Error( 'password_too_short', 'La contraseña debe tener al menos 8 caracteres.', [ 'status' => 400 ] ); } $user = get_userdata( $uid ); if ( ! wp_check_password( $current, $user->user_pass, $uid ) ) { return new WP_Error( 'wrong_password', 'Contraseña actual incorrecta.', [ 'status' => 400 ] ); } wp_set_password( $new_pw, $uid ); return rest_ensure_response( [ 'ok' => true, 'msg' => 'Contraseña actualizada correctamente.' ] ); } // POST /driver/update-profile public function rest_update_profile( $req ) { $uid = get_current_user_id(); $phone = sanitize_text_field( $req->get_param('phone') ?: '' ); $vehicle = sanitize_text_field( $req->get_param('vehicle_type') ?: '' ); $plate = sanitize_text_field( $req->get_param('vehicle_plate') ?: '' ); $photo = esc_url_raw( $req->get_param('photo_url') ?: '' ); if ( $phone ) update_user_meta( $uid, 'phone', $phone ); if ( $vehicle ) update_user_meta( $uid, 'vehicle_type', $vehicle ); if ( $plate ) { update_user_meta( $uid, 'vehicle_plate', $plate ); update_user_meta( $uid, 'license_plate', $plate ); } if ( $photo ) update_user_meta( $uid, 'photo_url', $photo ); return rest_ensure_response( [ 'ok' => true, 'msg' => 'Perfil actualizado correctamente.' ] ); } // GET /driver/zones public function rest_zones( $req ) { global $wpdb; $rows = $wpdb->get_results( "SELECT id, title, description, lat, lng, radius_km, bonus_amount, bonus_currency, countdown_seconds, type, expires_at FROM {$wpdb->prefix}ab_zone_alerts WHERE active = 1 AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY created_at DESC", ARRAY_A ); return rest_ensure_response( $rows ?: [] ); } // POST /driver/zone-response public function rest_zone_response( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $zone_id = intval( $body['zone_id'] ?? 0 ); $resp = sanitize_text_field( $body['response'] ?? '' ); if ( ! $zone_id || ! in_array( $resp, [ 'going', 'not_going' ], true ) ) { return new WP_Error( 'bad_request', 'Invalid parameters.', [ 'status' => 400 ] ); } $wpdb->insert( "{$wpdb->prefix}ab_zone_alert_responses", [ 'zone_id' => $zone_id, 'driver_id' => $uid, 'response' => $resp, 'responded_at' => current_time( 'mysql' ), ] ); return rest_ensure_response( [ 'ok' => true ] ); } // POST /incident/sos public function rest_sos( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $lat = floatval( $body['lat'] ?? 0 ); $lng = floatval( $body['lng'] ?? 0 ); $trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' ); $chunk_index = isset( $body['chunk_index'] ) ? intval( $body['chunk_index'] ) : null; $audio_data = $body['audio_data'] ?? null; $incident_id = intval( $body['incident_id'] ?? 0 ); if ( ! $incident_id ) { // First call — create incident $trip_id = null; if ( $trip_uuid ) { $trip_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}ab_trips WHERE trip_uuid = %s LIMIT 1", $trip_uuid ) ); } $wpdb->insert( "{$wpdb->prefix}ab_incidents", [ 'driver_id' => $uid, 'trip_id' => $trip_id ?: null, 'lat' => $lat, 'lng' => $lng, 'status' => 'active', 'chunks_count'=> 0, 'incident_at' => current_time( 'mysql' ), ] ); $incident_id = $wpdb->insert_id; // Send admin email $admin_email = get_option( 'admin_email' ); $maps_link = "https://maps.google.com/?q={$lat},{$lng}"; $user = get_userdata( $uid ); $subject = '🚨 SOS ALERT — Autobooking Driver'; $message = "Driver: {$user->display_name} (ID: {$uid})\n"; $message .= "GPS: {$lat}, {$lng}\n"; $message .= "Google Maps: {$maps_link}\n"; if ( $trip_uuid ) $message .= "Trip UUID: {$trip_uuid}\n"; $message .= "Time: " . current_time( 'mysql' ) . "\n"; wp_mail( $admin_email, $subject, $message ); } // Save audio chunk if provided if ( $audio_data && $incident_id ) { $upload_dir = wp_upload_dir(); $dir = $upload_dir['basedir'] . '/ab-incidents/' . $incident_id; if ( ! file_exists( $dir ) ) { wp_mkdir_p( $dir ); } $idx = intval( $chunk_index ) ?: 0; $filename = $dir . '/chunk_' . $idx . '.webm'; $decoded = base64_decode( $audio_data ); if ( $decoded !== false ) { file_put_contents( $filename, $decoded ); } $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}ab_incidents SET chunks_count = chunks_count + 1 WHERE id = %d", $incident_id ) ); } return rest_ensure_response( [ 'ok' => true, 'incident_id' => $incident_id ] ); } // POST /telemetry/position public function rest_position( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $lat = floatval( $body['lat'] ?? 0 ); $lng = floatval( $body['lng'] ?? 0 ); $accuracy = intval( $body['accuracy'] ?? 0 ); $trip_id = intval( $body['trip_id'] ?? 0 ); // Update driver status $exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ab_driver_status WHERE driver_id = %d", $uid ) ); $now = current_time( 'mysql' ); $data = [ 'last_lat' => $lat, 'last_lng' => $lng, 'last_accuracy' => $accuracy, 'last_seen' => $now, ]; if ( $exists ) { $wpdb->update( "{$wpdb->prefix}ab_driver_status", $data, [ 'driver_id' => $uid ] ); } else { $data['driver_id'] = $uid; $wpdb->insert( "{$wpdb->prefix}ab_driver_status", $data ); } // Insert trip position if trip_id given (table may not exist — catch quietly) if ( $trip_id ) { $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->prefix}ab_trip_positions (trip_id, driver_id, lat, lng, accuracy, recorded_at) VALUES (%d, %d, %s, %s, %d, %s)", $trip_id, $uid, $lat, $lng, $accuracy, $now ) ); } return rest_ensure_response( [ 'ok' => true ] ); } // POST /trip/start public function rest_trip_start( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' ); if ( ! $trip_uuid ) { return new WP_Error( 'bad_request', 'trip_uuid required.', [ 'status' => 400 ] ); } $affected = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}ab_trips SET status = 'in_progress', pickup_time = NOW(), updated_at = NOW() WHERE trip_uuid = %s AND driver_id = %d AND status = 'waiting'", $trip_uuid, $uid ) ); if ( ! $affected ) { return new WP_Error( 'not_found', 'Trip not found or invalid state.', [ 'status' => 404 ] ); } return rest_ensure_response( [ 'ok' => true ] ); } // POST /trip/finish public function rest_trip_finish( $req ) { global $wpdb; $uid = get_current_user_id(); $body = $req->get_json_params(); $trip_uuid = sanitize_text_field( $body['trip_uuid'] ?? '' ); $tolls = floatval( $body['tolls_amount'] ?? 0 ); $tips = floatval( $body['tips_amount'] ?? 0 ); $final_amount = isset( $body['final_amount'] ) ? floatval( $body['final_amount'] ) : null; if ( ! $trip_uuid ) { return new WP_Error( 'bad_request', 'trip_uuid required.', [ 'status' => 400 ] ); } // Fetch trip to recalculate payout $trip = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ab_trips WHERE trip_uuid = %s AND driver_id = %d", $trip_uuid, $uid ) ); if ( ! $trip ) { return new WP_Error( 'not_found', 'Trip not found.', [ 'status' => 404 ] ); } $payout = $final_amount !== null ? $final_amount : ( floatval( $trip->driver_payout_amount ) + $tolls + $tips ); $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}ab_trips SET status = 'finished', dropoff_time = NOW(), updated_at = NOW(), tolls_amount = %f, driver_payout_amount = %f WHERE trip_uuid = %s AND driver_id = %d", $tolls, $payout, $trip_uuid, $uid ) ); // Clear current_trip_id in driver status $wpdb->update( "{$wpdb->prefix}ab_driver_status", [ 'current_trip_id' => null ], [ 'driver_id' => $uid ] ); return rest_ensure_response( [ 'ok' => true ] ); } /* ───────────────────────────────────────────────────────────── SHORTCODE RENDER ───────────────────────────────────────────────────────────── */ public function render( $atts ) { if ( ! is_user_logged_in() ) { return '
🔒 Acceso Restringido
' . 'Debes iniciar sesión para acceder al panel de conductor.
' . 'Iniciar sesión' . '🚫 Acceso Denegado
' . 'Esta página es exclusiva para conductores registrados.
' . 'Conéctate para recibir viajes