'GET', 'callback' => array( $this, 'get_branches' ), 'permission_callback' => '__return_true', ) ); // GET /car-classes-all (all defined classes, no params needed) register_rest_route( self::NAMESPACE, '/car-classes-all', array( 'methods' => 'GET', 'callback' => array( $this, 'get_all_car_classes' ), 'permission_callback' => '__return_true', ) ); // GET /segments-branches-map (cached segment→branches mapping) register_rest_route( self::NAMESPACE, '/segments-branches-map', array( 'methods' => 'GET', 'callback' => array( $this, 'get_segments_branches_map' ), 'permission_callback' => '__return_true', ) ); // POST /car-classes register_rest_route( self::NAMESPACE, '/car-classes', array( 'methods' => 'POST', 'callback' => array( $this, 'get_car_classes' ), 'permission_callback' => array( $this, 'check_nonce' ), 'args' => array( 'dateFrom' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'dateTo' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'branchName' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), ), ) ); // POST /car-models register_rest_route( self::NAMESPACE, '/car-models', array( 'methods' => 'POST', 'callback' => array( $this, 'get_car_models' ), 'permission_callback' => array( $this, 'check_nonce' ), 'args' => array( 'dateFrom' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'dateTo' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'branchName' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'category' => array( 'required' => false, 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ), ), ) ); // POST /pricelist register_rest_route( self::NAMESPACE, '/pricelist', array( 'methods' => 'POST', 'callback' => array( $this, 'get_pricelist' ), 'permission_callback' => array( $this, 'check_nonce' ), 'args' => array( 'category' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'dateFrom' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'dateTo' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'pickUpLocation' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), ), ) ); // POST /pricing-summary register_rest_route( self::NAMESPACE, '/pricing-summary', array( 'methods' => 'POST', 'callback' => array( $this, 'get_pricing_summary' ), 'permission_callback' => array( $this, 'check_nonce' ), ) ); // POST /customer register_rest_route( self::NAMESPACE, '/customer', array( 'methods' => 'POST', 'callback' => array( $this, 'add_customer' ), 'permission_callback' => array( $this, 'check_nonce' ), ) ); // POST /booking register_rest_route( self::NAMESPACE, '/booking', array( 'methods' => 'POST', 'callback' => array( $this, 'make_booking' ), 'permission_callback' => array( $this, 'check_nonce' ), ) ); // POST /booking/confirm register_rest_route( self::NAMESPACE, '/booking/confirm', array( 'methods' => 'POST', 'callback' => array( $this, 'confirm_booking' ), 'permission_callback' => array( $this, 'check_nonce' ), 'args' => array( 'reservationId' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), ), ) ); // POST /booking/cancel register_rest_route( self::NAMESPACE, '/booking/cancel', array( 'methods' => 'POST', 'callback' => array( $this, 'cancel_booking' ), 'permission_callback' => array( $this, 'check_nonce' ), 'args' => array( 'reservationId' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), 'reason' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ), ), ) ); // GET /branches-full (cached, full branch objects with descriptions) register_rest_route( self::NAMESPACE, '/branches-full', array( 'methods' => 'GET', 'callback' => array( $this, 'get_branches_full' ), 'permission_callback' => '__return_true', ) ); // GET /agreements register_rest_route( self::NAMESPACE, '/agreements', array( 'methods' => 'GET', 'callback' => array( $this, 'get_agreements' ), 'permission_callback' => '__return_true', ) ); // GET /protection-packages register_rest_route( self::NAMESPACE, '/protection-packages', array( 'methods' => 'GET', 'callback' => array( $this, 'get_protection_packages' ), 'permission_callback' => '__return_true', ) ); } /** * Verify WP REST nonce for POST requests. */ public function check_nonce( WP_REST_Request $request ) { $nonce = $request->get_header( 'X-WP-Nonce' ); if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { return new WP_Error( 'rest_forbidden', __( 'Invalid nonce.', 'carei-reservation' ), array( 'status' => 403 ) ); } return true; } /** * Get API instance or return error. */ private function api() { $api = Carei_Softra_API::get_instance(); if ( null === $api ) { return new WP_Error( 'carei_not_configured', __( 'Softra API not configured.', 'carei-reservation' ), array( 'status' => 500 ) ); } return $api; } /** * Wrap API response. */ private function respond( $result ) { if ( is_wp_error( $result ) ) { return $result; } return new WP_REST_Response( $result, 200 ); } // ─── Callbacks ──────────────────────────────────────────────── public function get_segments_branches_map( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_segments_branches_map() ); } public function get_all_car_classes( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_all_car_classes() ); } public function get_branches( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_branches() ); } public function get_car_classes( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_car_classes( $request->get_param( 'dateFrom' ), $request->get_param( 'dateTo' ), $request->get_param( 'branchName' ) ) ); } public function get_car_models( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_car_models( $request->get_param( 'dateFrom' ), $request->get_param( 'dateTo' ), $request->get_param( 'branchName' ), $request->get_param( 'category' ) ) ); } public function get_pricelist( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } $pricelists = $api->get_pricelist( $request->get_param( 'category' ), $request->get_param( 'dateFrom' ), $request->get_param( 'dateTo' ), $request->get_param( 'pickUpLocation' ) ); // Auto-collect PL extra names + per-locale translate (Phase 19). if ( is_array( $pricelists ) ) { $locale = $this->resolve_locale( $request ); $translations = Carei_Admin_Panel::get_extras_translations(); foreach ( $pricelists as &$pricelist ) { if ( ! is_array( $pricelist ) || empty( $pricelist['additionalItems'] ) || ! is_array( $pricelist['additionalItems'] ) ) continue; foreach ( $pricelist['additionalItems'] as &$item ) { if ( ! is_array( $item ) || ! isset( $item['name'] ) || ! is_string( $item['name'] ) ) continue; $pl_name = trim( $item['name'] ); if ( $pl_name === '' ) continue; Carei_Admin_Panel::remember_extra_name( $pl_name ); if ( $locale === 'en' && isset( $translations[ $pl_name ] ) && $translations[ $pl_name ] !== '' ) { $item['name'] = $translations[ $pl_name ]; } } unset( $item ); } unset( $pricelist ); } return $this->respond( $pricelists ); } public function get_pricing_summary( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } $params = $request->get_json_params(); return $this->respond( $api->get_pricing_summary( $params ) ); } public function add_customer( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } $data = $request->get_json_params(); return $this->respond( $api->add_customer( $data ) ); } public function make_booking( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } $data = $request->get_json_params(); $result = $api->make_booking( $data ); // Save reservation to WP on success (fire-and-forget) if ( ! is_wp_error( $result ) && isset( $result['success'] ) && $result['success'] ) { Carei_Admin_Panel::save_reservation( $data, $result ); } return $this->respond( $result ); } public function confirm_booking( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->confirm_booking( $request->get_param( 'reservationId' ) ) ); } public function cancel_booking( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->cancel_booking( $request->get_param( 'reservationId' ), $request->get_param( 'reason' ) ) ); } public function get_branches_full( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_branches_cached() ); } public function get_agreements( WP_REST_Request $request ) { $api = $this->api(); if ( is_wp_error( $api ) ) { return $api; } return $this->respond( $api->get_agreements() ); } public function get_protection_packages( WP_REST_Request $request ) { $all = Carei_Admin_Panel::get_protection_packages(); $locale = $this->resolve_locale( $request ); $out = array( 'soft' => null, 'premium' => null ); foreach ( array( 'soft', 'premium' ) as $key ) { if ( isset( $all[ $key ] ) && ! empty( $all[ $key ]['active'] ) ) { $pkg = $all[ $key ]; if ( 'en' === $locale ) { $name = ! empty( $pkg['name_en'] ) ? $pkg['name_en'] : $pkg['name']; $desc = ! empty( $pkg['description_en'] ) ? $pkg['description_en'] : $pkg['description']; } else { $name = $pkg['name']; $desc = $pkg['description']; } $out[ $key ] = array( 'key' => $key, 'name' => $name, 'pricePerDay' => (float) $pkg['pricePerDay'], 'description' => $desc, ); } } return rest_ensure_response( $out ); } private function resolve_locale( WP_REST_Request $request ) { $lang = $request->get_param( 'lang' ); if ( $lang && in_array( strtolower( $lang ), array( 'pl', 'en' ), true ) ) { return strtolower( $lang ); } $locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale(); return ( 0 === strpos( (string) $locale, 'en' ) ) ? 'en' : 'pl'; } }