t ? 'yes' : 'no', $merchant_sent ? 'yes' : 'no' ) ); return false; } return true; } /** * Send the customer order withdrawal acknowledgement email. * * @param array $data Form data. * @param int $submitted_at Unix timestamp for the submission. */ private function send_customer_order_withdrawal_email( array $data, int $submitted_at ): bool { $subject = __( 'We received your withdrawal request', 'woocommerce' ); $heading = __( 'We received your withdrawal request', 'woocommerce' ); $body = '

' . esc_html__( 'We have received your request to withdraw from the order below.', 'woocommerce' ) . '

'; $body .= $this->get_email_details_html( $data, $submitted_at ); $body .= '

' . esc_html__( 'We will review your request and contact you about next steps, including any refund due.', 'woocommerce' ) . '

'; return wc_mail( $data[ self::FIELD_EMAIL ], $subject, $this->wrap_email_message( $heading, $body ) ); } /** * Send the merchant order withdrawal notification email. * * @param array $data Form data. * @param WC_Order|null $matched_order Matched order, if found. * @param int $submitted_at Unix timestamp for the submission. */ private function send_merchant_order_withdrawal_email( array $data, ?WC_Order $matched_order, int $submitted_at ): bool { $recipient = sanitize_email( (string) get_option( 'admin_email' ) ); if ( '' === $recipient || ! is_email( $recipient ) ) { return false; } $subject = sprintf( /* translators: %s: order number. */ __( 'Order withdrawal request for order %s', 'woocommerce' ), $data[ self::FIELD_ORDER_NUMBER ] ); $heading = __( 'Order withdrawal request received', 'woocommerce' ); $body = '

' . esc_html__( 'A customer submitted an order withdrawal request.', 'woocommerce' ) . '

'; if ( $matched_order instanceof WC_Order ) { $body .= '

' . esc_html__( 'WooCommerce matched this request to an order and added an order note.', 'woocommerce' ) . '

'; } else { $body .= '

' . esc_html__( 'WooCommerce could not match this request to an order automatically, so no order note was added.', 'woocommerce' ) . '

'; } $body .= $this->get_email_details_html( $data, $submitted_at ); if ( $matched_order instanceof WC_Order ) { $order_url = $matched_order->get_edit_order_url(); if ( $this->is_order_outside_withdrawal_window( $matched_order ) ) { $body .= '

' . esc_html( $this->get_withdrawal_window_warning_message() ) . '

'; } $body .= sprintf( '

%s

', sprintf( /* translators: %d: order ID. */ esc_html__( 'Matched order ID: %d', 'woocommerce' ), $matched_order->get_id() ) ); if ( '' !== $order_url ) { $body .= sprintf( '

%2$s

', esc_url( $order_url ), esc_html__( 'View matched order', 'woocommerce' ) ); } } return wc_mail( $recipient, $subject, $this->wrap_email_message( $heading, $body ), $this->get_merchant_email_headers( $data ) ); } /** * Get merchant email headers. * * @param array $data Form data. * @return string */ private function get_merchant_email_headers( array $data ): string { $headers = array( 'Content-Type: text/html; charset=UTF-8' ); $name = $this->get_customer_name( $data ); $email = $data[ self::FIELD_EMAIL ]; if ( '' !== $name && is_email( $email ) ) { $headers[] = sprintf( 'Reply-To: %1$s <%2$s>', $name, $email ); } return implode( "\r\n", $headers ); } /** * Wrap an email body in the WooCommerce email template. * * @param string $heading Email heading. * @param string $body Email body. */ private function wrap_email_message( string $heading, string $body ): string { return WC()->mailer()->wrap_message( $heading, $body ); } /** * Get the email details list. * * @param array $data Form data. * @param int $submitted_at Unix timestamp for the submission. */ private function get_email_details_html( array $data, int $submitted_at ): string { $date_format = (string) get_option( 'date_format' ); $time_format = (string) get_option( 'time_format' ); $additional_details = '' === $data[ self::FIELD_ADDITIONAL_DETAILS ] ? __( 'None provided', 'woocommerce' ) : $data[ self::FIELD_ADDITIONAL_DETAILS ]; $submitted_at_text = wp_date( trim( $date_format . ' ' . $time_format ), $submitted_at ); if ( false === $submitted_at_text ) { $submitted_at_text = ''; } $rows = array( __( 'Submitted', 'woocommerce' ) => $submitted_at_text, __( 'Name', 'woocommerce' ) => $this->get_customer_name( $data ), __( 'Email address', 'woocommerce' ) => $data[ self::FIELD_EMAIL ], __( 'Order number', 'woocommerce' ) => $data[ self::FIELD_ORDER_NUMBER ], __( 'Withdrawing', 'woocommerce' ) => $this->get_withdrawal_type_label( $data[ self::FIELD_WITHDRAWAL_TYPE ] ), __( 'Additional details', 'woocommerce' ) => $additional_details, ); $html = '
    '; foreach ( $rows as $label => $value ) { $html .= sprintf( '
  • %1$s: %2$s
  • ', esc_html( $label ), nl2br( esc_html( $value ) ) ); } $html .= '
'; return $html; } /** * Get the customer's full name for display. * * @param array $data Form data. */ private function get_customer_name( array $data ): string { return trim( $data[ self::FIELD_FIRST_NAME ] . ' ' . $data[ self::FIELD_LAST_NAME ] ); } /** * Get the label for a withdrawal type value. * * @param string $withdrawal_type Withdrawal type value. */ private function get_withdrawal_type_label( string $withdrawal_type ): string { $options = array( self::WITHDRAWAL_TYPE_FULL => __( 'The full order', 'woocommerce' ), self::WITHDRAWAL_TYPE_SPECIFIC => __( 'Specific items only', 'woocommerce' ), ); return $options[ $withdrawal_type ] ?? ''; } /** * Log an email failure. * * @param Throwable|string $error Email error. */ private function log_email_error( $error ): void { $message = $error instanceof Throwable ? $error->getMessage() : $error; wc_get_logger()->warning( sprintf( 'Order withdrawal email failed: %s', $message ), array( 'source' => self::LOGGER_SOURCE ) ); } /** * Log an inbox note failure without failing the submission. * * @param Throwable $e Inbox note error. * @param int $order_id Order ID. */ private function log_inbox_note_error( Throwable $e, int $order_id ): void { wc_get_logger()->warning( sprintf( 'Order withdrawal inbox note could not be processed for order %1$d. Error: %2$s', $order_id, $e->getMessage() ), array( 'source' => self::LOGGER_SOURCE ) ); } /** * Log an order note failure without failing the submission. * * @param WC_Order $order Matched order. * @param Throwable|null $e Order note error. */ private function log_order_note_error( WC_Order $order, ?Throwable $e = null ): void { $message = sprintf( 'Order withdrawal note could not be added to order %d.', $order->get_id() ); if ( $e instanceof Throwable ) { $message .= sprintf( ' Error: %s', $e->getMessage() ); } wc_get_logger()->warning( $message, array( 'source' => self::LOGGER_SOURCE ) ); } /** * Log an order meta failure without failing the submission. * * @param WC_Order $order Matched order. * @param Throwable $e Order meta error. */ private function log_order_meta_error( WC_Order $order, Throwable $e ): void { wc_get_logger()->warning( sprintf( 'Order withdrawal meta flag could not be added to order %1$d. Error: %2$s', $order->get_id(), $e->getMessage() ), array( 'source' => self::LOGGER_SOURCE ) ); } }