Commit f8c4691f4dc for woocommerce

commit f8c4691f4dc670ca1284efe3b8f2e8fa7da55bb9
Author: Little Fedya <littfed@gmail.com>
Date:   Wed Oct 7 13:06:49 2026 +0500

    fix: prevent errors when saving coupons with empty post_date_gmt (#69010)

    * fix: backfill date_created when saving coupon with empty post_date_gmt

    * fix: only set post_date and post_date_gmt when date_created is present

    * test: ensure coupon fixture uses draft status in empty post_date_gmt test

    * chore: update PHPStan baseline for coupon date_created guard

    The guard in WC_Coupon_Data_Store_CPT::update() removes one of three
    baselined null dereferences for each of getOffsetTimestamp() and
    getTimestamp(), so lower both counts to 2.

    Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Jan Lysý <jan.lysy@automattic.com>
    Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-69000-coupon-empty-post-date-gmt b/plugins/woocommerce/changelog/fix-69000-coupon-empty-post-date-gmt
new file mode 100644
index 00000000000..9bbf2112c86
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-69000-coupon-empty-post-date-gmt
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix an error that stopped some draft coupons from being saved.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-coupon-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-coupon-data-store-cpt.php
index 3c7a3eb42b8..c4b7f552bfe 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-coupon-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-coupon-data-store-cpt.php
@@ -186,6 +186,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
 	 */
 	public function update( &$coupon ) {
 		$coupon->save_meta_data();
+
 		$changes = $coupon->get_changes();

 		if ( array_intersect( array( 'code', 'description', 'date_created', 'date_modified', 'status' ), array_keys( $changes ) ) ) {
@@ -193,12 +194,15 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
 				'post_title'        => $coupon->get_code( 'edit' ),
 				'post_excerpt'      => $coupon->get_description( 'edit' ),
 				'post_status'       => $coupon->get_status( 'edit' ),
-				'post_date'         => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getOffsetTimestamp() ),
-				'post_date_gmt'     => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getTimestamp() ),
 				'post_modified'     => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $coupon->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
 				'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $coupon->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
 			);

+			if ( $coupon->get_date_created( 'edit' ) ) {
+				$post_data['post_date']     = gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getOffsetTimestamp() );
+				$post_data['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getTimestamp() );
+			}
+
 			/**
 			 * When updating this object, to prevent infinite loops, use $wpdb
 			 * to update data, since wp_update_post spawns more calls to the
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 98358c4b961..948d44c9e2b 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -16731,13 +16731,13 @@ parameters:
 		-
 			message: '#^Cannot call method getOffsetTimestamp\(\) on WC_DateTime\|null\.$#'
 			identifier: method.nonObject
-			count: 3
+			count: 2
 			path: includes/data-stores/class-wc-coupon-data-store-cpt.php

 		-
 			message: '#^Cannot call method getTimestamp\(\) on WC_DateTime\|null\.$#'
 			identifier: method.nonObject
-			count: 3
+			count: 2
 			path: includes/data-stores/class-wc-coupon-data-store-cpt.php

 		-
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-coupon-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-coupon-data-store-cpt-test.php
index e8ab9fa67fb..e3eefc5de7f 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-coupon-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-coupon-data-store-cpt-test.php
@@ -357,4 +357,35 @@ class WC_Coupon_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 			'A subclass reading the deprecated property must keep seeing the historical accumulated list after saves.'
 		);
 	}
+
+	/**
+	 * @testdox Should update coupon with empty post_date_gmt without error and without pinning date_created.
+	 */
+	public function test_update_coupon_with_empty_post_date_gmt(): void {
+		global $wpdb;
+
+		$coupon    = $this->create_settled_coupon();
+		$coupon_id = $coupon->get_id();
+
+		// Simulate empty post_date_gmt as found in unpublished or draft posts.
+		$wpdb->update(
+			$wpdb->posts,
+			array(
+				'post_status'   => 'draft',
+				'post_date_gmt' => '0000-00-00 00:00:00',
+			),
+			array( 'ID' => $coupon_id )
+		);
+		clean_post_cache( $coupon_id );
+
+		$coupon = new WC_Coupon( $coupon_id );
+		$this->assertNull( $coupon->get_date_created( 'edit' ) );
+
+		$coupon->set_description( 'Updated description' );
+		$coupon->save();
+
+		$reloaded_coupon = new WC_Coupon( $coupon_id );
+		$this->assertNull( $reloaded_coupon->get_date_created( 'edit' ) );
+		$this->assertSame( 'Updated description', $reloaded_coupon->get_description() );
+	}
 }