'utf-8',
'format' => 'A4',
'margin_left' => 15,
'margin_right' => 15,
'margin_top' => 15,
'margin_bottom' => 20,
'tempDir' => $temp_dir,
)
);
$mpdf->SetTitle( sprintf( 'Invoice %s', $invoice_number ) );
$mpdf->SetAuthor( get_option( 'wp_bnb_business_name', get_bloginfo( 'name' ) ) );
$mpdf->SetCreator( 'WP BnB' );
$mpdf->WriteHTML( $html );
// Save to file.
$file_path = self::get_invoice_path( $order, $invoice_number );
$mpdf->Output( $file_path, 'F' );
// Store invoice number and path in order meta.
$order->update_meta_data( '_bnb_invoice_number', $invoice_number );
$order->update_meta_data( '_bnb_invoice_path', $file_path );
$order->update_meta_data( '_bnb_invoice_date', current_time( 'mysql' ) );
$order->save();
/**
* Fires after generating an invoice.
*
* @param \WC_Order $order WooCommerce order.
* @param string $file_path Invoice file path.
*/
do_action( 'wp_bnb_wc_after_invoice_generate', $order, $file_path );
return $file_path;
} catch ( \Exception $e ) {
error_log( 'WP BnB Invoice generation failed: ' . $e->getMessage() );
return null;
}
}
/**
* Get invoice number for an order.
*
* @param \WC_Order $order WooCommerce order.
* @return string Invoice number.
*/
public static function get_invoice_number( \WC_Order $order ): string {
// Check if already has invoice number.
$existing = $order->get_meta( '_bnb_invoice_number', true );
if ( $existing ) {
return $existing;
}
// Generate new invoice number.
return Manager::get_next_invoice_number();
}
/**
* Get invoice file path.
*
* @param \WC_Order $order WooCommerce order.
* @param string $invoice_number Invoice number.
* @return string File path.
*/
private static function get_invoice_path( \WC_Order $order, string $invoice_number ): string {
$upload_dir = wp_upload_dir();
$invoice_dir = $upload_dir['basedir'] . '/' . self::INVOICE_DIR;
// Create directory if needed.
if ( ! file_exists( $invoice_dir ) ) {
wp_mkdir_p( $invoice_dir );
// Add .htaccess to protect invoices.
$htaccess = $invoice_dir . '/.htaccess';
if ( ! file_exists( $htaccess ) ) {
file_put_contents( $htaccess, 'Deny from all' );
}
// Add index.php for extra protection.
$index = $invoice_dir . '/index.php';
if ( ! file_exists( $index ) ) {
file_put_contents( $index, 'get_id() . '.pdf';
}
/**
* Check if invoice exists for an order.
*
* @param \WC_Order $order WooCommerce order.
* @return bool
*/
public static function invoice_exists( \WC_Order $order ): bool {
$path = $order->get_meta( '_bnb_invoice_path', true );
return $path && file_exists( $path );
}
/**
* Attach invoice to email.
*
* @param array $attachments Attachments array.
* @param string $email_id Email ID.
* @param \WC_Order $order WooCommerce order.
* @param \WC_Email $email Email object.
* @return array Modified attachments.
*/
public static function attach_invoice_to_email( array $attachments, string $email_id, $order, $email ): array {
// Only attach to specific emails.
$allowed_emails = array(
'customer_completed_order',
'customer_processing_order',
'customer_invoice',
);
if ( ! in_array( $email_id, $allowed_emails, true ) ) {
return $attachments;
}
// Check if order is a WC_Order.
if ( ! $order instanceof \WC_Order ) {
return $attachments;
}
// Check if invoice attachment is enabled.
if ( ! Manager::is_invoice_attach_enabled() ) {
return $attachments;
}
// Check if this order has a booking.
$booking_id = Manager::get_booking_for_order( $order );
if ( ! $booking_id ) {
return $attachments;
}
// Generate invoice if it doesn't exist.
if ( ! self::invoice_exists( $order ) ) {
self::generate_invoice( $order );
}
// Get invoice path.
$invoice_path = $order->get_meta( '_bnb_invoice_path', true );
if ( $invoice_path && file_exists( $invoice_path ) ) {
$attachments[] = $invoice_path;
}
return $attachments;
}
/**
* Add order action button for invoice.
*
* @param \WC_Order $order WooCommerce order.
* @return void
*/
public static function add_order_action_button( \WC_Order $order ): void {
// Check if this order has a booking.
$booking_id = Manager::get_booking_for_order( $order );
if ( ! $booking_id ) {
return;
}
// Generate download URL.
$download_url = add_query_arg(
array(
'bnb_download_invoice' => $order->get_id(),
'_wpnonce' => wp_create_nonce( 'bnb_download_invoice_' . $order->get_id() ),
),
admin_url( 'admin.php' )
);
?>
__( 'Permission denied.', 'wp-bnb' ) ) );
}
$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
if ( ! $order_id ) {
wp_send_json_error( array( 'message' => __( 'Invalid order ID.', 'wp-bnb' ) ) );
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
wp_send_json_error( array( 'message' => __( 'Order not found.', 'wp-bnb' ) ) );
}
$file_path = self::generate_invoice( $order );
if ( $file_path ) {
wp_send_json_success( array( 'message' => __( 'Invoice generated successfully.', 'wp-bnb' ) ) );
} else {
wp_send_json_error( array( 'message' => __( 'Failed to generate invoice.', 'wp-bnb' ) ) );
}
}
/**
* Handle invoice download request.
*
* @return void
*/
public static function handle_invoice_download(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['bnb_download_invoice'] ) ) {
return;
}
$order_id = absint( $_GET['bnb_download_invoice'] );
// Verify nonce.
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bnb_download_invoice_' . $order_id ) ) {
wp_die( esc_html__( 'Security check failed.', 'wp-bnb' ) );
}
// Check capabilities.
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to download invoices.', 'wp-bnb' ) );
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
wp_die( esc_html__( 'Order not found.', 'wp-bnb' ) );
}
// Generate invoice if needed.
if ( ! self::invoice_exists( $order ) ) {
self::generate_invoice( $order );
}
$invoice_path = $order->get_meta( '_bnb_invoice_path', true );
if ( ! $invoice_path || ! file_exists( $invoice_path ) ) {
wp_die( esc_html__( 'Invoice not found.', 'wp-bnb' ) );
}
$invoice_number = $order->get_meta( '_bnb_invoice_number', true );
$filename = 'invoice-' . sanitize_file_name( $invoice_number ) . '.pdf';
// Output file.
header( 'Content-Type: application/pdf' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Content-Length: ' . filesize( $invoice_path ) );
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
readfile( $invoice_path );
exit;
}
/**
* Get invoice HTML content.
*
* @param \WC_Order $order WooCommerce order.
* @param string $invoice_number Invoice number.
* @param int|null $booking_id Booking ID.
* @return string HTML content.
*/
private static function get_invoice_html( \WC_Order $order, string $invoice_number, ?int $booking_id ): string {
// Get business info.
$business = self::get_business_info();
// Get booking details.
$check_in = '';
$check_out = '';
$room_name = '';
$building_name = '';
$nights = 0;
$guests = 0;
if ( $booking_id ) {
$check_in = get_post_meta( $booking_id, '_bnb_booking_check_in', true );
$check_out = get_post_meta( $booking_id, '_bnb_booking_check_out', true );
$guests = get_post_meta( $booking_id, '_bnb_booking_adults', true );
$room_id = get_post_meta( $booking_id, '_bnb_booking_room_id', true );
if ( $room_id ) {
$room = get_post( $room_id );
$room_name = $room ? $room->post_title : '';
$building = Room::get_building( $room_id );
$building_name = $building ? $building->post_title : '';
}
if ( $check_in && $check_out ) {
$check_in_date = \DateTime::createFromFormat( 'Y-m-d', $check_in );
$check_out_date = \DateTime::createFromFormat( 'Y-m-d', $check_out );
if ( $check_in_date && $check_out_date ) {
$nights = $check_in_date->diff( $check_out_date )->days;
}
}
}
// Format dates.
$date_format = get_option( 'date_format' );
$check_in_display = $check_in ? date_i18n( $date_format, strtotime( $check_in ) ) : '';
$check_out_display = $check_out ? date_i18n( $date_format, strtotime( $check_out ) ) : '';
$order_date = $order->get_date_created() ? $order->get_date_created()->date_i18n( $date_format ) : '';
// Get logo.
$logo_html = '';
$logo_id = Manager::get_invoice_logo();
if ( $logo_id ) {
$logo_url = wp_get_attachment_url( $logo_id );
if ( $logo_url ) {
$logo_html = '';
}
}
// Get footer.
$footer_text = Manager::get_invoice_footer();
// Build HTML.
$html = '
' . esc_html( $invoice_number ) . '
'; $html .= '' . esc_html( $order_date ) . '
'; $html .= '' . esc_html( $business['name'] ) . '
'; if ( $business['street'] ) { $html .= '' . esc_html( $business['street'] ) . '
'; } if ( $business['city'] || $business['postal'] ) { $html .= '' . esc_html( $business['postal'] . ' ' . $business['city'] ) . '
'; } if ( $business['country'] ) { $html .= '' . esc_html( $business['country'] ) . '
'; } if ( $business['email'] ) { $html .= '' . esc_html( $business['email'] ) . '
'; } if ( $business['phone'] ) { $html .= '' . esc_html( $business['phone'] ) . '
'; } $html .= '' . esc_html( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ) . '
'; if ( $order->get_billing_address_1() ) { $html .= '' . esc_html( $order->get_billing_address_1() ) . '
'; } if ( $order->get_billing_city() || $order->get_billing_postcode() ) { $html .= '' . esc_html( $order->get_billing_postcode() . ' ' . $order->get_billing_city() ) . '
'; } if ( $order->get_billing_country() ) { $html .= '' . esc_html( WC()->countries->countries[ $order->get_billing_country() ] ?? $order->get_billing_country() ) . '
'; } if ( $order->get_billing_email() ) { $html .= '' . esc_html( $order->get_billing_email() ) . '
'; } $html .= '| ' . esc_html__( 'Room', 'wp-bnb' ) . ' | ' . esc_html( $room_name ); if ( $building_name ) { $html .= ' (' . esc_html( $building_name ) . ')'; } $html .= ' |
| ' . esc_html__( 'Check-in', 'wp-bnb' ) . ' | ' . esc_html( $check_in_display ) . ' |
| ' . esc_html__( 'Check-out', 'wp-bnb' ) . ' | ' . esc_html( $check_out_display ) . ' |
| ' . esc_html__( 'Nights', 'wp-bnb' ) . ' | ' . esc_html( $nights ) . ' |
| ' . esc_html__( 'Guests', 'wp-bnb' ) . ' | ' . esc_html( $guests ) . ' |
| ' . esc_html__( 'Description', 'wp-bnb' ) . ' | '; $html .= '' . esc_html__( 'Qty', 'wp-bnb' ) . ' | '; $html .= '' . esc_html__( 'Price', 'wp-bnb' ) . ' | '; $html .= '' . esc_html__( 'Total', 'wp-bnb' ) . ' | '; $html .= '
|---|---|---|---|
| ' . esc_html( $item->get_name() ) . ' | '; $html .= '' . esc_html( $qty ) . ' | '; $html .= '' . wc_price( $price ) . ' | '; $html .= '' . wc_price( $total ) . ' | '; $html .= '
| ' . esc_html__( 'Subtotal', 'wp-bnb' ) . ' | ' . wc_price( $order->get_subtotal() ) . ' |
| ' . esc_html__( 'Tax', 'wp-bnb' ) . ' | ' . wc_price( $order->get_total_tax() ) . ' |
| ' . esc_html__( 'Total', 'wp-bnb' ) . ' | ' . wc_price( $order->get_total() ) . ' |
' . esc_html__( 'Payment Status:', 'wp-bnb' ) . ' '; if ( $order->is_paid() ) { $html .= '' . esc_html__( 'PAID', 'wp-bnb' ) . ''; } else { $html .= '' . esc_html__( 'PENDING', 'wp-bnb' ) . ''; } $html .= '
'; $html .= '' . esc_html__( 'Payment Method:', 'wp-bnb' ) . ' ' . esc_html( $order->get_payment_method_title() ) . '
'; $html .= '