Files
wp-bnb/src/Api/Controllers/GuestsController.php

453 lines
13 KiB
PHP
Raw Normal View History

<?php
/**
* Guests REST Controller
*
* Handles REST API endpoints for guests (admin only).
*
* @package Magdev\WpBnb\Api\Controllers
*/
declare( strict_types=1 );
namespace Magdev\WpBnb\Api\Controllers;
use Magdev\WpBnb\PostTypes\Guest;
use Magdev\WpBnb\PostTypes\Booking;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;
/**
* Guests Controller class.
*/
final class GuestsController extends AbstractController {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'guests';
/**
* Register routes.
*
* @return void
*/
public function register_routes(): void {
// GET /guests - List guests (admin).
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'admin_permission' ),
'args' => $this->get_collection_params(),
),
)
);
// GET /guests/search - Search guests (admin).
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/search',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'search_guests' ),
'permission_callback' => array( $this, 'admin_permission' ),
'args' => array(
'q' => array(
'description' => __( 'Search query (name, email, phone).', 'wp-bnb' ),
'type' => 'string',
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
),
'limit' => array(
'description' => __( 'Maximum results.', 'wp-bnb' ),
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 50,
'sanitize_callback' => 'absint',
),
),
),
)
);
// GET /guests/{id} - Get single guest (admin).
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'admin_permission' ),
'args' => array(
'id' => array(
'description' => __( 'Guest ID.', 'wp-bnb' ),
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
),
),
)
);
// GET /guests/{id}/bookings - Get guest's bookings (admin).
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)/bookings',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_guest_bookings' ),
'permission_callback' => array( $this, 'admin_permission' ),
'args' => array(
'id' => array(
'description' => __( 'Guest ID.', 'wp-bnb' ),
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
),
),
)
);
}
/**
* Get collection of guests.
*
* @param WP_REST_Request $request Current request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
public function get_items( $request ) {
$rate_limit_error = $this->check_rate_limit( $request );
if ( $rate_limit_error ) {
return $rate_limit_error;
}
$pagination = $this->get_pagination_params( $request );
$sorting = $this->get_sorting_params( $request, array( 'title', 'date' ), 'title' );
$args = array(
'post_type' => Guest::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => $pagination['per_page'],
'offset' => $pagination['offset'],
'orderby' => $sorting['orderby'],
'order' => $sorting['order'],
);
// Search filter.
$search = $request->get_param( 'search' );
if ( $search ) {
$args['s'] = $search;
}
// Status filter.
$status = $request->get_param( 'status' );
if ( $status ) {
$args['meta_query'] = array(
array(
'key' => '_bnb_guest_status',
'value' => $status,
),
);
}
$query = new \WP_Query( $args );
$items = array();
foreach ( $query->posts as $post ) {
$items[] = $this->prepare_guest_response( $post );
}
$response = $this->formatter->collection(
$items,
$query->found_posts,
$pagination['page'],
$pagination['per_page']
);
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Search guests by name, email, or phone.
*
* @param WP_REST_Request $request Current request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
public function search_guests( $request ) {
$rate_limit_error = $this->check_rate_limit( $request );
if ( $rate_limit_error ) {
return $rate_limit_error;
}
$query = $request->get_param( 'q' );
$limit = $request->get_param( 'limit' );
// Search by title (name) first.
$args = array(
'post_type' => Guest::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => $limit,
's' => $query,
);
$results = get_posts( $args );
// Also search by email and phone if we have room for more results.
if ( count( $results ) < $limit ) {
$existing_ids = wp_list_pluck( $results, 'ID' );
$remaining = $limit - count( $results );
// Search by email.
$email_args = array(
'post_type' => Guest::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => $remaining,
'post__not_in' => $existing_ids,
'meta_query' => array(
array(
'key' => '_bnb_guest_email',
'value' => $query,
'compare' => 'LIKE',
),
),
);
$email_results = get_posts( $email_args );
$results = array_merge( $results, $email_results );
// Search by phone if still room.
if ( count( $results ) < $limit ) {
$existing_ids = wp_list_pluck( $results, 'ID' );
$remaining = $limit - count( $results );
$phone_args = array(
'post_type' => Guest::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => $remaining,
'post__not_in' => $existing_ids,
'meta_query' => array(
array(
'key' => '_bnb_guest_phone',
'value' => $query,
'compare' => 'LIKE',
),
),
);
$phone_results = get_posts( $phone_args );
$results = array_merge( $results, $phone_results );
}
}
$items = array();
foreach ( $results as $post ) {
$items[] = $this->prepare_guest_summary( $post );
}
$response = $this->formatter->success( $items );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Get single guest.
*
* @param WP_REST_Request $request Current request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
public function get_item( $request ) {
$rate_limit_error = $this->check_rate_limit( $request );
if ( $rate_limit_error ) {
return $rate_limit_error;
}
$id = $request->get_param( 'id' );
$post = get_post( $id );
if ( ! $post || Guest::POST_TYPE !== $post->post_type ) {
return $this->formatter->not_found( __( 'Guest', 'wp-bnb' ) );
}
$data = $this->prepare_guest_response( $post, true );
$response = $this->formatter->success( $data );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Get guest's booking history.
*
* @param WP_REST_Request $request Current request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
public function get_guest_bookings( $request ) {
$rate_limit_error = $this->check_rate_limit( $request );
if ( $rate_limit_error ) {
return $rate_limit_error;
}
$guest_id = $request->get_param( 'id' );
$guest = get_post( $guest_id );
if ( ! $guest || Guest::POST_TYPE !== $guest->post_type ) {
return $this->formatter->not_found( __( 'Guest', 'wp-bnb' ) );
}
$bookings = Guest::get_bookings( $guest_id );
$items = array();
foreach ( $bookings as $booking ) {
$room_id = get_post_meta( $booking->ID, '_bnb_booking_room_id', true );
$room = $room_id ? get_post( $room_id ) : null;
$check_in = get_post_meta( $booking->ID, '_bnb_booking_check_in', true );
$check_out = get_post_meta( $booking->ID, '_bnb_booking_check_out', true );
$items[] = array(
'id' => $booking->ID,
'reference' => get_post_meta( $booking->ID, '_bnb_booking_reference', true ) ?: $booking->post_title,
'room' => $room ? array(
'id' => $room->ID,
'title' => get_the_title( $room ),
) : null,
'check_in' => $check_in,
'check_out' => $check_out,
'status' => get_post_meta( $booking->ID, '_bnb_booking_status', true ),
'total' => (float) get_post_meta( $booking->ID, '_bnb_booking_total_price', true ),
'created_at' => $booking->post_date_gmt,
'_links' => array(
'self' => array(
array( 'href' => rest_url( $this->namespace . '/bookings/' . $booking->ID ) ),
),
),
);
}
$response = $this->formatter->success( $items );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Prepare guest data for response.
*
* @param \WP_Post $post Guest post object.
* @param bool $full Include full details.
* @return array Guest data.
*/
private function prepare_guest_response( \WP_Post $post, bool $full = false ): array {
$data = array(
'id' => $post->ID,
'first_name' => get_post_meta( $post->ID, '_bnb_guest_first_name', true ),
'last_name' => get_post_meta( $post->ID, '_bnb_guest_last_name', true ),
'email' => get_post_meta( $post->ID, '_bnb_guest_email', true ),
'phone' => get_post_meta( $post->ID, '_bnb_guest_phone', true ),
'status' => get_post_meta( $post->ID, '_bnb_guest_status', true ) ?: 'active',
'created_at' => $post->post_date_gmt,
);
// Address.
$data['address'] = array(
'street' => get_post_meta( $post->ID, '_bnb_guest_street', true ),
'city' => get_post_meta( $post->ID, '_bnb_guest_city', true ),
'postal_code' => get_post_meta( $post->ID, '_bnb_guest_postal_code', true ),
'country' => get_post_meta( $post->ID, '_bnb_guest_country', true ),
);
// Statistics.
$booking_count = Guest::get_booking_count( $post->ID );
$total_spent = Guest::get_total_spent( $post->ID );
$data['statistics'] = array(
'total_bookings' => $booking_count,
'total_spent' => $total_spent,
);
if ( $full ) {
$data['nationality'] = get_post_meta( $post->ID, '_bnb_guest_nationality', true );
$data['date_of_birth'] = get_post_meta( $post->ID, '_bnb_guest_date_of_birth', true );
$data['notes'] = get_post_meta( $post->ID, '_bnb_guest_notes', true );
$data['preferences'] = get_post_meta( $post->ID, '_bnb_guest_preferences', true );
// Get last stay date.
$bookings = Guest::get_bookings( $post->ID );
if ( ! empty( $bookings ) ) {
$last_booking = $bookings[0];
$data['statistics']['last_stay'] = get_post_meta( $last_booking->ID, '_bnb_booking_check_out', true );
}
// Formatted address.
$data['address']['formatted'] = Guest::get_formatted_address( $post->ID );
// GDPR consent info.
$data['consent'] = array(
'data_processing' => (bool) get_post_meta( $post->ID, '_bnb_guest_consent_data', true ),
'marketing' => (bool) get_post_meta( $post->ID, '_bnb_guest_consent_marketing', true ),
'date' => get_post_meta( $post->ID, '_bnb_guest_consent_date', true ),
);
}
// Note: ID/passport numbers are NOT exposed via API for security.
$data['_links'] = array(
'self' => array(
array( 'href' => rest_url( $this->namespace . '/guests/' . $post->ID ) ),
),
'bookings' => array(
array( 'href' => rest_url( $this->namespace . '/guests/' . $post->ID . '/bookings' ) ),
),
);
return $data;
}
/**
* Prepare guest summary for search results.
*
* @param \WP_Post $post Guest post object.
* @return array Guest summary.
*/
private function prepare_guest_summary( \WP_Post $post ): array {
return array(
'id' => $post->ID,
'first_name' => get_post_meta( $post->ID, '_bnb_guest_first_name', true ),
'last_name' => get_post_meta( $post->ID, '_bnb_guest_last_name', true ),
'email' => get_post_meta( $post->ID, '_bnb_guest_email', true ),
'phone' => get_post_meta( $post->ID, '_bnb_guest_phone', true ),
'_links' => array(
'self' => array(
array( 'href' => rest_url( $this->namespace . '/guests/' . $post->ID ) ),
),
),
);
}
/**
* Get collection parameters with status filter.
*
* @return array Collection parameters.
*/
public function get_collection_params(): array {
$params = parent::get_collection_params();
$params['status'] = array(
'description' => __( 'Filter by guest status.', 'wp-bnb' ),
'type' => 'string',
'enum' => array( 'active', 'inactive', 'blocked' ),
'sanitize_callback' => 'sanitize_text_field',
);
return $params;
}
}