should_load_front_assets() ) { return; } wp_enqueue_style( 'ipscmi-order-cart-localpickup-classic', trailingslashit( IPSCMI_ORDER_CART_ASSETS ) . 'css/ips-front-woolocalpickup-style.css', [], IPSCMI_ORDER_CART_VERSION ); if ( wp_script_is( 'wc-checkout', 'registered' ) || wp_script_is( 'wc-checkout', 'enqueued' ) ) { $field_selector = sprintf( 'input[name="%s"]', esc_js( LocalPickupSession::FIELD_NAME ) ); wp_add_inline_script( 'wc-checkout', '(function($){$(document.body).on("change","' . $field_selector . '",function(){$(document.body).trigger("update_checkout");});})(jQuery);', 'after' ); } } public function render_checkout_location_selector( $rate, $index ): void { if ( ! $rate instanceof WC_Shipping_Rate || 'local_pickup' !== $rate->get_method_id() ) { return; } $locations = LocalPickupResolver::get_active_locations(); if ( [] === $locations ) { return; } $selected_shipping_method = $this->get_selected_shipping_method(); $is_selected_rate = $selected_shipping_method === $rate->get_id(); $selected_location_id = $this->resolve_selected_location_id( $locations ); echo LocalPickupRenderer::render_checkout_rate_markup( $locations, $selected_location_id, $is_selected_rate ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public function handle_checkout_update( string $posted_data ): void { parse_str( $posted_data, $parsed_data ); if ( ! is_array( $parsed_data ) ) { return; } LocalPickupSession::sync_from_posted_data( $parsed_data ); } /** * @param array $data * @param \WP_Error $errors */ public function validate_checkout_selection( array $data, $errors ): void { if ( function_exists( 'WC' ) && WC()->cart && ! WC()->cart->needs_shipping() ) { return; } $selected_shipping_method = LocalPickupSession::extract_selected_shipping_method( $data ); if ( ! LocalPickupSession::is_local_pickup_method( $selected_shipping_method ) ) { return; } try { $this->resolve_checkout_location( absint( $data[ LocalPickupSession::FIELD_NAME ] ?? 0 ) ); } catch ( \InvalidArgumentException $exception ) { $errors->add( 'ips_localpickup_missing', $exception->getMessage() ); } } /** * @param array $data */ public function persist_order_meta( WC_Order $order, array $data ): void { if ( ! $this->order_uses_local_pickup( $order ) ) { LocalPickupOrderData::clear( $order ); LocalPickupSession::clear_selected_location_id(); return; } $location = $this->resolve_checkout_location( absint( $data[ LocalPickupSession::FIELD_NAME ] ?? 0 ) ); if ( null === $location ) { LocalPickupOrderData::clear( $order ); } else { LocalPickupOrderData::write( $order, $location ); } } public function persist_store_api_order_meta( WC_Order $order, \WP_REST_Request $request ): void { // Only final cart checkout: draft updates and payment retries for an // existing /checkout/{id} order must not replace its pickup snapshot. if ( 'POST' !== $request->get_method() || 1 !== preg_match( '#/checkout/?$#', $request->get_route() ) ) { return; } try { $this->persist_order_meta( $order, [] ); } catch ( \InvalidArgumentException $exception ) { throw new RouteException( 'ips_localpickup_missing', $exception->getMessage(), 400 ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- REST error data; Woo serializes it and the checkout UI escapes its display. } // WooCommerce saves this order after the hook and before payment. } /** * @param int|WC_Order $order */ public function render_order_details( $order ): void { $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( ! $order instanceof WC_Order || ! $this->order_uses_local_pickup( $order ) ) { return; } $location = $this->get_order_location_payload( $order ); if ( null === $location || empty( $location['showOnThankYouPage'] ) ) { return; } echo LocalPickupRenderer::render_order_summary( $location ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public function render_admin_order_details( WC_Order $order ): void { if ( ! $this->order_uses_local_pickup( $order ) ) { return; } $location = LocalPickupOrderData::from_order( $order ); if ( null === $location ) { return; } echo '
'; echo '

' . esc_html__( 'Pickup location', 'ips-order-cart' ) . ':
' . esc_html( (string) $location['locationName'] ) . '

'; if ( ! empty( $location['addressText'] ) ) { echo '

' . nl2br( esc_html( (string) $location['addressText'] ) ) . '

'; } if ( ! empty( $location['estimatePickupTimeText'] ) ) { echo '

' . esc_html__( 'Estimated pickup time', 'ips-order-cart' ) . ': ' . esc_html( (string) $location['estimatePickupTimeText'] ) . '

'; } if ( ! empty( $location['instructionText'] ) ) { echo '

' . esc_html__( 'Pickup instructions', 'ips-order-cart' ) . ':
' . nl2br( esc_html( (string) $location['instructionText'] ) ) . '

'; } echo '
'; } /** * @param object $email */ public function render_email_details( WC_Order $order, bool $sent_to_admin, bool $plain_text, $email ): void { if ( ! $this->order_uses_local_pickup( $order ) ) { return; } $location = LocalPickupOrderData::from_order( $order ); if ( null === $location || empty( $location['showInEmails'] ) ) { return; } echo LocalPickupRenderer::render_email_summary( $location, $plain_text ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } private function should_load_front_assets(): bool { if ( is_admin() ) { return false; } if ( ! function_exists( 'is_checkout' ) ) { return false; } if ( is_checkout() || ( function_exists( 'is_checkout_pay_page' ) && is_checkout_pay_page() ) || ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) ) { return true; } return function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url( 'view-order' ); } private function get_selected_shipping_method(): string { if ( ! function_exists( 'WC' ) || ! WC()->session ) { return ''; } $chosen_methods = WC()->session->get( 'chosen_shipping_methods', [] ); if ( is_array( $chosen_methods ) && isset( $chosen_methods[0] ) ) { return (string) $chosen_methods[0]; } return ''; } /** * @param array> $locations */ private function resolve_selected_location_id( array $locations ): int { $selected_location_id = LocalPickupSession::get_selected_location_id(); if ( $selected_location_id > 0 && null !== LocalPickupResolver::get_active_location( $selected_location_id ) ) { return $selected_location_id; } $first_location = reset( $locations ); return is_array( $first_location ) ? (int) ( $first_location['id'] ?? 0 ) : 0; } /** * Resolve the displayed default only when there was no explicit selection. * Recheck both availability filters so Pro restrictions also remain binding. * * @return array|null */ private function resolve_checkout_location( int $location_id ): ?array { $location_id = $location_id > 0 ? $location_id : LocalPickupSession::get_selected_location_id(); $locations = LocalPickupResolver::get_active_locations(); if ( $location_id <= 0 && [] === $locations ) { return null; // Native Woo pickup without IPS locations remains usable. } if ( $location_id <= 0 ) { $location_id = (int) $locations[0]['id']; } $location = LocalPickupResolver::get_active_location( $location_id ); if ( null === $location || ! in_array( $location_id, array_map( 'intval', array_column( $locations, 'id' ) ), true ) ) { throw new \InvalidArgumentException( __( 'Please choose a pickup location.', 'ips-order-cart' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Validation message is escaped by the classic checkout or serialized as REST error data. } return $location; } private function order_uses_local_pickup( WC_Order $order ): bool { foreach ( $order->get_items( 'shipping' ) as $shipping_item ) { if ( ! $shipping_item instanceof WC_Order_Item_Shipping ) { continue; } if ( 'local_pickup' === $shipping_item->get_method_id() ) { return true; } } return false; } /** * @return array|null */ private function get_order_location_payload( WC_Order $order ): ?array { if ( function_exists( 'is_order_received_page' ) && ! is_order_received_page() ) { return null; } return LocalPickupOrderData::from_order( $order ); } }