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[\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[\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', ), ); } }