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

376 lines
11 KiB
PHP
Raw Normal View History

<?php
/**
* Services REST Controller
*
* Handles REST API endpoints for services.
*
* @package Magdev\WpBnb\Api\Controllers
*/
declare( strict_types=1 );
namespace Magdev\WpBnb\Api\Controllers;
use Magdev\WpBnb\PostTypes\Service;
use Magdev\WpBnb\Taxonomies\ServiceCategory;
use Magdev\WpBnb\Pricing\Calculator;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;
/**
* Services Controller class.
*/
final class ServicesController extends AbstractController {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'services';
/**
* Register routes.
*
* @return void
*/
public function register_routes(): void {
// GET /services - List active services (public).
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'public_permission' ),
'args' => $this->get_services_collection_params(),
),
)
);
// GET /services/{id} - Get single service (public).
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, 'public_permission' ),
'args' => array(
'id' => array(
'description' => __( 'Service ID.', 'wp-bnb' ),
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
),
),
)
);
// POST /services/{id}/calculate - Calculate service price.
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)/calculate',
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'calculate_price' ),
'permission_callback' => array( $this, 'public_permission' ),
'args' => array(
'id' => array(
'description' => __( 'Service ID.', 'wp-bnb' ),
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
'quantity' => array(
'description' => __( 'Quantity.', 'wp-bnb' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
),
'nights' => array(
'description' => __( 'Number of nights (for per-night services).', 'wp-bnb' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
),
),
),
)
);
}
/**
* Get collection of services.
*
* @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;
}
$args = array(
'post_type' => Service::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => 100, // Services typically don't need pagination.
'orderby' => 'meta_value_num',
'meta_key' => '_bnb_service_sort_order',
'order' => 'ASC',
);
$meta_query = array();
// Status filter (default: active only).
$status = $request->get_param( 'status' ) ?: 'active';
if ( 'all' !== $status ) {
$meta_query[] = array(
'key' => '_bnb_service_status',
'value' => $status,
);
}
// Pricing type filter.
$pricing_type = $request->get_param( 'pricing_type' );
if ( $pricing_type ) {
$meta_query[] = array(
'key' => '_bnb_service_pricing_type',
'value' => $pricing_type,
);
}
if ( ! empty( $meta_query ) ) {
$meta_query['relation'] = 'AND';
$args['meta_query'] = $meta_query;
}
// Category filter.
$category = $request->get_param( 'category' );
if ( $category ) {
$args['tax_query'] = array(
array(
'taxonomy' => 'bnb_service_category',
'field' => is_numeric( $category ) ? 'term_id' : 'slug',
'terms' => $category,
),
);
}
$services = get_posts( $args );
$items = array();
foreach ( $services as $service ) {
$items[] = $this->prepare_service_response( $service );
}
$response = $this->formatter->success( $items );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Get single service.
*
* @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 || Service::POST_TYPE !== $post->post_type || 'publish' !== $post->post_status ) {
return $this->formatter->not_found( __( 'Service', 'wp-bnb' ) );
}
$data = $this->prepare_service_response( $post, true );
$response = $this->formatter->success( $data );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Calculate service price.
*
* @param WP_REST_Request $request Current request.
* @return WP_REST_Response|WP_Error Response object or error.
*/
public function calculate_price( $request ) {
$rate_limit_error = $this->check_rate_limit( $request );
if ( $rate_limit_error ) {
return $rate_limit_error;
}
$service_id = $request->get_param( 'id' );
$quantity = $request->get_param( 'quantity' );
$nights = $request->get_param( 'nights' );
// Validate service.
$service = get_post( $service_id );
if ( ! $service || Service::POST_TYPE !== $service->post_type || 'publish' !== $service->post_status ) {
return $this->formatter->not_found( __( 'Service', 'wp-bnb' ) );
}
// Check if service is active.
$status = get_post_meta( $service_id, '_bnb_service_status', true );
if ( 'active' !== $status ) {
return $this->formatter->validation_error( 'id', __( 'Service is not available.', 'wp-bnb' ) );
}
// Check max quantity.
$max_quantity = (int) get_post_meta( $service_id, '_bnb_service_max_quantity', true ) ?: 1;
if ( $quantity > $max_quantity ) {
return $this->formatter->validation_error(
'quantity',
sprintf(
/* translators: %d: maximum quantity */
__( 'Maximum quantity is %d.', 'wp-bnb' ),
$max_quantity
)
);
}
// Calculate price.
$total = Service::calculate_service_price( $service_id, $quantity, $nights );
$pricing_type = get_post_meta( $service_id, '_bnb_service_pricing_type', true );
$unit_price = (float) get_post_meta( $service_id, '_bnb_service_price', true );
$currency = get_option( 'wp_bnb_currency', 'CHF' );
// Build calculation string.
$calculation = '';
switch ( $pricing_type ) {
case 'included':
$calculation = __( 'Included', 'wp-bnb' );
break;
case 'per_booking':
$calculation = sprintf(
'%s x %d',
Calculator::formatPrice( $unit_price ),
$quantity
);
break;
case 'per_night':
$calculation = sprintf(
'%s x %d x %d %s',
Calculator::formatPrice( $unit_price ),
$quantity,
$nights,
_n( 'night', 'nights', $nights, 'wp-bnb' )
);
break;
}
$data = array(
'service_id' => $service_id,
'quantity' => $quantity,
'nights' => $nights,
'unit_price' => $unit_price,
'total' => $total,
'formatted' => Calculator::formatPrice( $total ),
'currency' => $currency,
'calculation' => $calculation,
);
$response = $this->formatter->success( $data );
return $this->add_rate_limit_headers( $response, $request );
}
/**
* Prepare service data for response.
*
* @param \WP_Post $post Service post object.
* @param bool $full Include full details.
* @return array Service data.
*/
private function prepare_service_response( \WP_Post $post, bool $full = false ): array {
$pricing_type = get_post_meta( $post->ID, '_bnb_service_pricing_type', true );
$price = (float) get_post_meta( $post->ID, '_bnb_service_price', true );
$status = get_post_meta( $post->ID, '_bnb_service_status', true ) ?: 'active';
$max_quantity = (int) get_post_meta( $post->ID, '_bnb_service_max_quantity', true ) ?: 1;
$currency = get_option( 'wp_bnb_currency', 'CHF' );
$data = array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'slug' => $post->post_name,
'description' => get_the_excerpt( $post ),
'pricing' => array(
'type' => $pricing_type,
'price' => $price,
'formatted' => Service::format_service_price( Service::get_service_data( $post->ID ) ),
'currency' => $currency,
),
'max_quantity' => $max_quantity,
'status' => $status,
);
// Category.
$categories = wp_get_post_terms( $post->ID, 'bnb_service_category' );
if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
$category = $categories[0];
$data['category'] = array(
'id' => $category->term_id,
'name' => $category->name,
'slug' => $category->slug,
'icon' => get_term_meta( $category->term_id, '_bnb_service_category_icon', true ),
);
}
if ( $full ) {
$data['content'] = apply_filters( 'the_content', $post->post_content );
$data['sort_order'] = (int) get_post_meta( $post->ID, '_bnb_service_sort_order', true );
}
$data['_links'] = array(
'self' => array(
array( 'href' => rest_url( $this->namespace . '/services/' . $post->ID ) ),
),
);
return $data;
}
/**
* Get services collection parameters.
*
* @return array Collection parameters.
*/
private function get_services_collection_params(): array {
return array(
'status' => array(
'description' => __( 'Filter by status (default: active).', 'wp-bnb' ),
'type' => 'string',
'enum' => array( 'active', 'inactive', 'all' ),
'default' => 'active',
'sanitize_callback' => 'sanitize_text_field',
),
'pricing_type' => array(
'description' => __( 'Filter by pricing type.', 'wp-bnb' ),
'type' => 'string',
'enum' => array( 'included', 'per_booking', 'per_night' ),
'sanitize_callback' => 'sanitize_text_field',
),
'category' => array(
'description' => __( 'Filter by category (term ID or slug).', 'wp-bnb' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}