Commit cac3015c8f7 for woocommerce

commit cac3015c8f724ca001e2d7a2107d2f0acad48757
Author: Ján Mikláš <neosinner@gmail.com>
Date:   Mon Sep 14 09:47:04 2026 +0200

    Guard the Orders report export against a missing customer name (#68393)

    * fix: guard the Orders report export against a missing customer name

    prepare_item_for_export() reads extended_info.customer for every row, but
    that key defaults to an empty array when the order has no analytics
    customer record, and a guest order carries only the name read off the
    order itself. get_customer_name() indexed first_name and last_name
    unguarded, so those rows raised "Undefined array key" and exported a lone
    space instead of a blank cell.

    Read both keys defensively and trim the result.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01SEcWbxtTF4Lmy1uWR8RGYw

    * Better changelog

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

    * Clearer docblock

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

    * Fix Orders export docblock spacing

    * test: use order status constant in export fixture

    ---------

    Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard b/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard
new file mode 100644
index 00000000000..51b757f1d33
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent the Orders Analytics CSV export from logging warnings and writing a lone space when an order has no customer name.
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
index d2d49f9d316..d0b2075f22c 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
@@ -414,11 +414,14 @@ class Controller extends GenericController implements ExportableInterface {
 	/**
 	 * Get customer name column export value.
 	 *
+	 * Analytics rows can contain an empty customer array when the customer record is missing, and guest
+	 * orders may include only a first or last name, so neither key is guaranteed.
+	 *
 	 * @param array $customer Customer from report row.
 	 * @return string
 	 */
 	protected function get_customer_name( $customer ) {
-		return $customer['first_name'] . ' ' . $customer['last_name'];
+		return trim( ( $customer['first_name'] ?? '' ) . ' ' . ( $customer['last_name'] ?? '' ) );
 	}

 	/**
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php
new file mode 100644
index 00000000000..ea69505bdfe
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php
@@ -0,0 +1,87 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\API\Reports\Orders;
+
+use Automattic\WooCommerce\Admin\API\Reports\Orders\Controller;
+use Automattic\WooCommerce\Enums\OrderStatus;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the Orders report export methods.
+ */
+class ControllerTest extends WC_Unit_Test_Case {
+
+	/**
+	 * The System Under Test.
+	 *
+	 * @var Controller
+	 */
+	private $sut;
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->sut = new Controller();
+	}
+
+	/**
+	 * @testdox The customer name export column should be empty for an order with no customer record.
+	 */
+	public function test_customer_name_is_empty_when_the_order_has_no_customer_record(): void {
+		$export_item = $this->sut->prepare_item_for_export( $this->get_item( array() ) );
+
+		$this->assertSame( '', $export_item['customer_name'] );
+	}
+
+	/**
+	 * @testdox The customer name export column should hold the name a guest order carries on its own.
+	 */
+	public function test_customer_name_is_read_from_a_partial_customer_record(): void {
+		$export_item = $this->sut->prepare_item_for_export(
+			$this->get_item(
+				array(
+					'first_name' => 'Ada',
+					'last_name'  => 'Lovelace',
+				)
+			)
+		);
+
+		$this->assertSame( 'Ada Lovelace', $export_item['customer_name'] );
+	}
+
+	/**
+	 * @testdox The customer name export column should hold a first name on its own.
+	 */
+	public function test_customer_name_holds_a_first_name_on_its_own(): void {
+		$export_item = $this->sut->prepare_item_for_export( $this->get_item( array( 'first_name' => 'Ada' ) ) );
+
+		$this->assertSame( 'Ada', $export_item['customer_name'] );
+	}
+
+	/**
+	 * Build a report row carrying the given customer record.
+	 *
+	 * @param array $customer Customer record for the row.
+	 * @return array
+	 */
+	private function get_item( array $customer ): array {
+		return array(
+			'date'            => '2026-09-04 10:00:00',
+			'order_number'    => '123',
+			'total_formatted' => '10.00',
+			'status'          => OrderStatus::COMPLETED,
+			'customer_type'   => 'new',
+			'num_items_sold'  => 1,
+			'net_total'       => 10.00,
+			'extended_info'   => array(
+				'products'    => array(),
+				'coupons'     => array(),
+				'customer'    => $customer,
+				'attribution' => array( 'origin' => 'Direct' ),
+			),
+		);
+	}
+}