453 lines
13 KiB
PHP
453 lines
13 KiB
PHP
|
|
<?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;
|
||
|
|
}
|
||
|
|
}
|