From 983f4169b1475859c13e0ca4d0aea7951341ec72 Mon Sep 17 00:00:00 2001 From: David Stone Date: Wed, 25 Mar 2026 11:16:46 -0600 Subject: [PATCH 1/2] fix: override to_array() in 7 models to populate lazy-loaded meta properties REST API was returning null for all meta-stored fields (customer_id, membership_id, type, active, featured_image_id, categories, line_items, invoice_number, event, schedule, notice_type, migrated_from_id, etc.) because Base_Model::to_array() uses get_object_vars() which captures the raw unloaded null values before any getter has been called. Fix: override to_array() in each affected model to call the lazy-loading getters before delegating to parent::to_array(). This follows the existing pattern used by Discount_Code and Event models. Also fixes Site::to_array() to expose id = blog_id (non-zero) instead of the default 0 that get_object_vars() returns for the id property. Affected models: Site, Product, Customer, Payment, Checkout_Form, Email, Broadcast. Closes #469 --- inc/models/class-broadcast.php | 18 ++++++++ inc/models/class-checkout-form.php | 18 ++++++++ inc/models/class-customer.php | 18 ++++++++ inc/models/class-email.php | 18 ++++++++ inc/models/class-payment.php | 18 ++++++++ inc/models/class-product.php | 26 +++++++++++ inc/models/class-site.php | 30 +++++++++++++ tests/WP_Ultimo/Models/Broadcast_Test.php | 23 ++++++++++ tests/WP_Ultimo/Models/Checkout_Form_Test.php | 23 ++++++++++ tests/WP_Ultimo/Models/Customer_Test.php | 26 +++++++++++ tests/WP_Ultimo/Models/Email_Test.php | 22 ++++++++++ tests/WP_Ultimo/Models/Payment_Test.php | 23 ++++++++++ tests/WP_Ultimo/Models/Product_Test.php | 44 +++++++++++++++++++ tests/WP_Ultimo/Models/Site_Test.php | 30 +++++++++++++ 14 files changed, 337 insertions(+) diff --git a/inc/models/class-broadcast.php b/inc/models/class-broadcast.php index 75cd1e255..dcd748d09 100644 --- a/inc/models/class-broadcast.php +++ b/inc/models/class-broadcast.php @@ -285,4 +285,22 @@ public function set_status($status): void { $this->status = $status; } + + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_migrated_from_id(); + $this->get_notice_type(); + + return parent::to_array(); + } } diff --git a/inc/models/class-checkout-form.php b/inc/models/class-checkout-form.php index 59279e3ea..ad9bc039f 100644 --- a/inc/models/class-checkout-form.php +++ b/inc/models/class-checkout-form.php @@ -1625,4 +1625,22 @@ public static function add_new_site_form_fields() { return apply_filters('wu_checkout_form_add_new_site_form_fields', $steps); } + + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_thank_you_page_id(); + $this->get_conversion_snippets(); + + return parent::to_array(); + } } diff --git a/inc/models/class-customer.php b/inc/models/class-customer.php index 75dfcc1e1..a2741fc56 100644 --- a/inc/models/class-customer.php +++ b/inc/models/class-customer.php @@ -962,4 +962,22 @@ public function set_network_id($network_id): void { $this->network_id = $network_id ? absint($network_id) : null; } + + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->has_trialed(); + $this->get_extra_information(); + + return parent::to_array(); + } } diff --git a/inc/models/class-email.php b/inc/models/class-email.php index a965aa6fa..dab319c6c 100644 --- a/inc/models/class-email.php +++ b/inc/models/class-email.php @@ -818,4 +818,22 @@ public function set_legacy($legacy): void { $this->meta[ self::META_LEGACY ] = $legacy; } + + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_event(); + $this->has_schedule(); + + return parent::to_array(); + } } diff --git a/inc/models/class-payment.php b/inc/models/class-payment.php index 447442ea8..cc2d1563f 100644 --- a/inc/models/class-payment.php +++ b/inc/models/class-payment.php @@ -1158,4 +1158,22 @@ public function duplicate() { return $new_payment; } + + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_line_items(); + $this->get_invoice_number(); + + return parent::to_array(); + } } diff --git a/inc/models/class-product.php b/inc/models/class-product.php index b597d994c..b3dafee25 100644 --- a/inc/models/class-product.php +++ b/inc/models/class-product.php @@ -1586,6 +1586,32 @@ public function get_price_variation($duration, $duration_unit) { return apply_filters('wu_product_get_price_variation', $price_variation, $duration, $duration_unit, $this); } + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization so REST API responses include all fields. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_featured_image_id(); + $this->get_tax_category(); + $this->get_contact_us_label(); + $this->get_contact_us_link(); + $this->get_feature_list(); + $this->get_available_addons(); + $this->get_legacy_options(); + $this->get_pwyw_minimum_amount(); + $this->get_pwyw_suggested_amount(); + $this->get_pwyw_recurring_mode(); + + return parent::to_array(); + } + /** * Save (create or update) the model on the database. * diff --git a/inc/models/class-site.php b/inc/models/class-site.php index 4c3c8521f..1b06ee4eb 100644 --- a/inc/models/class-site.php +++ b/inc/models/class-site.php @@ -1696,6 +1696,36 @@ function ($replace_list, $from_site_id, $to_site_id) use ($transient) { // phpcs } } + /** + * Transform the object into an assoc array. + * + * Overrides Base_Model::to_array() to ensure lazy-loaded meta properties + * are populated before serialization. Without this, properties such as + * customer_id, membership_id, type, active, etc. remain null in REST API + * responses because get_object_vars() captures the raw (unloaded) values. + * + * @since 2.0.11 + * @return array + */ + public function to_array() { + + // Trigger lazy-loading for all meta-backed properties. + $this->get_customer_id(); + $this->get_membership_id(); + $this->get_template_id(); + $this->get_featured_image_id(); + $this->get_categories(); + $this->get_type(); + $this->is_active(); + + $array = parent::to_array(); + + // Expose blog_id as id so callers get the correct non-zero value. + $array['id'] = $this->get_id(); + + return $array; + } + /** * Save (create or update) the model on the database. * diff --git a/tests/WP_Ultimo/Models/Broadcast_Test.php b/tests/WP_Ultimo/Models/Broadcast_Test.php index 8291e43e0..925f65c79 100644 --- a/tests/WP_Ultimo/Models/Broadcast_Test.php +++ b/tests/WP_Ultimo/Models/Broadcast_Test.php @@ -301,4 +301,27 @@ public function test_query_class(): void { $this->assertEquals(\WP_Ultimo\Database\Broadcasts\Broadcast_Query::class, $query_class); } + + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * migrated_from_id and notice_type are only loaded from meta when their + * getter is first called. Without the to_array() override they remain null. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $broadcast = new \WP_Ultimo\Models\Broadcast(); + $broadcast->set_type('broadcast_notice'); + $broadcast->set_notice_type('warning'); + $broadcast->set_migrated_from_id(99); + + $array = $broadcast->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + $this->assertArrayHasKey('notice_type', $array, 'to_array() must include notice_type.'); + $this->assertNotNull($array['notice_type'], 'notice_type must not be null in to_array() output.'); + $this->assertEquals('warning', $array['notice_type'], 'notice_type must match the set value.'); + $this->assertArrayHasKey('migrated_from_id', $array, 'to_array() must include migrated_from_id.'); + $this->assertNotNull($array['migrated_from_id'], 'migrated_from_id must not be null in to_array() output.'); + $this->assertEquals(99, $array['migrated_from_id'], 'migrated_from_id must match the set value.'); + } } diff --git a/tests/WP_Ultimo/Models/Checkout_Form_Test.php b/tests/WP_Ultimo/Models/Checkout_Form_Test.php index 7936613ba..c9fd79d4a 100644 --- a/tests/WP_Ultimo/Models/Checkout_Form_Test.php +++ b/tests/WP_Ultimo/Models/Checkout_Form_Test.php @@ -2195,4 +2195,27 @@ public function test_get_field_returns_extra_attributes(): void { $this->assertEquals('you@example.com', $field['placeholder']); $this->assertEquals('Enter your email', $field['tooltip']); } + + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * thank_you_page_id and conversion_snippets are only loaded from meta when + * their getter is first called. Without the to_array() override they remain null. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $form = new \WP_Ultimo\Models\Checkout_Form(); + $form->set_name('Test Form'); + $form->set_slug('test-form'); + $form->set_thank_you_page_id(42); + $form->set_conversion_snippets(''); + + $array = $form->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + $this->assertArrayHasKey('thank_you_page_id', $array, 'to_array() must include thank_you_page_id.'); + $this->assertNotNull($array['thank_you_page_id'], 'thank_you_page_id must not be null in to_array() output.'); + $this->assertEquals(42, $array['thank_you_page_id'], 'thank_you_page_id must match the set value.'); + $this->assertArrayHasKey('conversion_snippets', $array, 'to_array() must include conversion_snippets.'); + $this->assertNotNull($array['conversion_snippets'], 'conversion_snippets must not be null in to_array() output.'); + } } diff --git a/tests/WP_Ultimo/Models/Customer_Test.php b/tests/WP_Ultimo/Models/Customer_Test.php index f1e9801d0..7624df1d6 100644 --- a/tests/WP_Ultimo/Models/Customer_Test.php +++ b/tests/WP_Ultimo/Models/Customer_Test.php @@ -403,4 +403,30 @@ public function tearDown(): void { parent::tearDown(); } + + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * has_trialed and extra_information are only loaded from meta when their + * getter is first called. Without the to_array() override they remain null. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $user_id = self::factory()->user->create(['user_email' => 'lazy@example.com']); + + $customer = new Customer(); + $customer->set_user_id($user_id); + $customer->set_type('customer'); + $customer->set_email_verification('none'); + $customer->set_date_registered('2023-01-01 00:00:00'); + $customer->set_has_trialed(false); + $customer->set_extra_information(['key' => 'value']); + + $array = $customer->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + $this->assertArrayHasKey('has_trialed', $array, 'to_array() must include has_trialed.'); + $this->assertNotNull($array['has_trialed'], 'has_trialed must not be null in to_array() output.'); + $this->assertArrayHasKey('extra_information', $array, 'to_array() must include extra_information.'); + $this->assertNotNull($array['extra_information'], 'extra_information must not be null in to_array() output.'); + } } diff --git a/tests/WP_Ultimo/Models/Email_Test.php b/tests/WP_Ultimo/Models/Email_Test.php index 3dd2750f8..17e109882 100644 --- a/tests/WP_Ultimo/Models/Email_Test.php +++ b/tests/WP_Ultimo/Models/Email_Test.php @@ -1740,4 +1740,26 @@ public function tearDown(): void { parent::tearDown(); } + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * event and schedule are only loaded from meta when their getter is first + * called. Without the to_array() override they remain null. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $email = new \WP_Ultimo\Models\Email(); + $email->set_slug('test-email'); + $email->set_event('user_registration'); + $email->set_schedule(true); + + $array = $email->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + $this->assertArrayHasKey('event', $array, 'to_array() must include event.'); + $this->assertNotNull($array['event'], 'event must not be null in to_array() output.'); + $this->assertEquals('user_registration', $array['event'], 'event must match the set value.'); + $this->assertArrayHasKey('schedule', $array, 'to_array() must include schedule.'); + $this->assertNotNull($array['schedule'], 'schedule must not be null in to_array() output.'); + } + } diff --git a/tests/WP_Ultimo/Models/Payment_Test.php b/tests/WP_Ultimo/Models/Payment_Test.php index f3128795d..a68abf643 100644 --- a/tests/WP_Ultimo/Models/Payment_Test.php +++ b/tests/WP_Ultimo/Models/Payment_Test.php @@ -1577,4 +1577,27 @@ public static function tear_down_after_class() { $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}wu_payments"); parent::tear_down_after_class(); } + + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * line_items and invoice_number are only loaded from meta when their + * getter is first called. Without the to_array() override they remain null. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $payment = new \WP_Ultimo\Models\Payment(); + $payment->set_status('pending'); + $payment->set_currency('USD'); + $payment->set_subtotal(100); + $payment->set_total(100); + $payment->set_invoice_number('INV-001'); + + $array = $payment->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + $this->assertArrayHasKey('line_items', $array, 'to_array() must include line_items.'); + $this->assertArrayHasKey('invoice_number', $array, 'to_array() must include invoice_number.'); + $this->assertNotNull($array['invoice_number'], 'invoice_number must not be null in to_array() output.'); + $this->assertEquals('INV-001', $array['invoice_number'], 'invoice_number must match the set value.'); + } } diff --git a/tests/WP_Ultimo/Models/Product_Test.php b/tests/WP_Ultimo/Models/Product_Test.php index 993c5ff64..4e4668ff2 100644 --- a/tests/WP_Ultimo/Models/Product_Test.php +++ b/tests/WP_Ultimo/Models/Product_Test.php @@ -291,6 +291,50 @@ public function test_to_array(): void { $this->assertArrayNotHasKey('meta', $array, 'Array should not contain meta.'); } + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * featured_image_id, tax_category, contact_us_label, contact_us_link, + * feature_list, available_addons, legacy_options, and pwyw fields are only + * loaded from meta when their getter is first called. Without the to_array() + * override they remain null in REST API responses. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $product = new \WP_Ultimo\Models\Product(); + $product->set_name('Test Product'); + $product->set_slug('test-product'); + $product->set_amount(50); + $product->set_tax_category('standard'); + $product->set_contact_us_label('Contact Us'); + $product->set_contact_us_link('https://example.com/contact'); + $product->set_pwyw_minimum_amount(10.0); + $product->set_pwyw_suggested_amount(25.0); + $product->set_pwyw_recurring_mode('customer_choice'); + + $array = $product->to_array(); + + $this->assertIsArray($array, 'to_array() should return an array.'); + + $this->assertArrayHasKey('tax_category', $array, 'to_array() must include tax_category.'); + $this->assertNotNull($array['tax_category'], 'tax_category must not be null in to_array() output.'); + $this->assertEquals('standard', $array['tax_category'], 'tax_category must match the set value.'); + + $this->assertArrayHasKey('contact_us_label', $array, 'to_array() must include contact_us_label.'); + $this->assertNotNull($array['contact_us_label'], 'contact_us_label must not be null in to_array() output.'); + + $this->assertArrayHasKey('contact_us_link', $array, 'to_array() must include contact_us_link.'); + $this->assertNotNull($array['contact_us_link'], 'contact_us_link must not be null in to_array() output.'); + + $this->assertArrayHasKey('pwyw_minimum_amount', $array, 'to_array() must include pwyw_minimum_amount.'); + $this->assertEquals(10.0, $array['pwyw_minimum_amount'], 'pwyw_minimum_amount must match the set value.'); + + $this->assertArrayHasKey('pwyw_suggested_amount', $array, 'to_array() must include pwyw_suggested_amount.'); + $this->assertEquals(25.0, $array['pwyw_suggested_amount'], 'pwyw_suggested_amount must match the set value.'); + + $this->assertArrayHasKey('pwyw_recurring_mode', $array, 'to_array() must include pwyw_recurring_mode.'); + $this->assertEquals('customer_choice', $array['pwyw_recurring_mode'], 'pwyw_recurring_mode must match the set value.'); + } + /** * Test hash generation. */ diff --git a/tests/WP_Ultimo/Models/Site_Test.php b/tests/WP_Ultimo/Models/Site_Test.php index 484965dfa..a9bc7e312 100644 --- a/tests/WP_Ultimo/Models/Site_Test.php +++ b/tests/WP_Ultimo/Models/Site_Test.php @@ -440,6 +440,36 @@ public function test_to_array(): void { $this->assertArrayNotHasKey('meta', $array, 'Array should not contain meta.'); } + /** + * Test that to_array() populates lazy-loaded meta properties (issue #469). + * + * Before the fix, get_object_vars() captured null for properties that are + * only loaded from meta when their getter is first called. This caused the + * REST API to return null for customer_id, membership_id, type, etc. + */ + public function test_to_array_includes_lazy_loaded_meta_properties(): void { + $customer_id = $this->customer->get_id(); + + $this->site->set_customer_id($customer_id); + $this->site->set_type('customer_owned'); + + $array = $this->site->to_array(); + + // id must equal blog_id (non-zero). + $this->assertNotEquals(0, $array['id'], 'to_array() id must equal blog_id, not 0.'); + + // Lazy-loaded fields must not be null. + $this->assertArrayHasKey('customer_id', $array, 'to_array() must include customer_id.'); + $this->assertNotNull($array['customer_id'], 'customer_id must not be null in to_array() output.'); + + $this->assertArrayHasKey('type', $array, 'to_array() must include type.'); + $this->assertNotNull($array['type'], 'type must not be null in to_array() output.'); + + $this->assertArrayHasKey('active', $array, 'to_array() must include active.'); + // active defaults to true when not explicitly set. + $this->assertNotNull($array['active'], 'active must not be null in to_array() output.'); + } + /** * Test hash generation. */ From ff2071d8f13d22c57ec5eb6d31109581e6291a37 Mon Sep 17 00:00:00 2001 From: David Stone Date: Wed, 25 Mar 2026 12:01:09 -0600 Subject: [PATCH 2/2] fix: guard to_array() lazy-loading with get_id() to prevent validation failure on unsaved models The to_array() overrides introduced in 983f416 unconditionally called lazy-loading getters (get_featured_image_id, get_tax_category, etc.) which trigger get_meta() calls. For unsaved models (no ID), get_meta() returns default values like false instead of null. When validate() calls to_array() during save(), these false values fail validation rules (e.g. 'integer' rejects false), causing save() to return WP_Error. This broke the E2E checkout tests: setup-product.php silently failed to save the test product (failOnNonZeroExit: false masked the error), so the pricing table had no products to render, causing Cypress to time out waiting for '#wrapper-field-pricing_table label[id^="wu-product-"]'. Fix: wrap lazy-loading calls in if ($this->get_id()) so they only execute for persisted models where meta is actually available. Closes #469 --- inc/models/class-broadcast.php | 10 +++++++--- inc/models/class-checkout-form.php | 10 +++++++--- inc/models/class-customer.php | 10 +++++++--- inc/models/class-email.php | 10 +++++++--- inc/models/class-payment.php | 10 +++++++--- inc/models/class-product.php | 27 ++++++++++++++++----------- inc/models/class-site.php | 21 +++++++++++++-------- 7 files changed, 64 insertions(+), 34 deletions(-) diff --git a/inc/models/class-broadcast.php b/inc/models/class-broadcast.php index dcd748d09..59de60327 100644 --- a/inc/models/class-broadcast.php +++ b/inc/models/class-broadcast.php @@ -297,9 +297,13 @@ public function set_status($status): void { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_migrated_from_id(); - $this->get_notice_type(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values that can fail validation rules. + if ($this->get_id()) { + $this->get_migrated_from_id(); + $this->get_notice_type(); + } return parent::to_array(); } diff --git a/inc/models/class-checkout-form.php b/inc/models/class-checkout-form.php index ad9bc039f..638110868 100644 --- a/inc/models/class-checkout-form.php +++ b/inc/models/class-checkout-form.php @@ -1637,9 +1637,13 @@ public static function add_new_site_form_fields() { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_thank_you_page_id(); - $this->get_conversion_snippets(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values that can fail validation rules. + if ($this->get_id()) { + $this->get_thank_you_page_id(); + $this->get_conversion_snippets(); + } return parent::to_array(); } diff --git a/inc/models/class-customer.php b/inc/models/class-customer.php index a2741fc56..edcaff051 100644 --- a/inc/models/class-customer.php +++ b/inc/models/class-customer.php @@ -974,9 +974,13 @@ public function set_network_id($network_id): void { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->has_trialed(); - $this->get_extra_information(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values that can fail validation rules. + if ($this->get_id()) { + $this->has_trialed(); + $this->get_extra_information(); + } return parent::to_array(); } diff --git a/inc/models/class-email.php b/inc/models/class-email.php index dab319c6c..cd0001eb5 100644 --- a/inc/models/class-email.php +++ b/inc/models/class-email.php @@ -830,9 +830,13 @@ public function set_legacy($legacy): void { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_event(); - $this->has_schedule(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values that can fail validation rules. + if ($this->get_id()) { + $this->get_event(); + $this->has_schedule(); + } return parent::to_array(); } diff --git a/inc/models/class-payment.php b/inc/models/class-payment.php index cc2d1563f..157da2f0c 100644 --- a/inc/models/class-payment.php +++ b/inc/models/class-payment.php @@ -1170,9 +1170,13 @@ public function duplicate() { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_line_items(); - $this->get_invoice_number(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values that can fail validation rules. + if ($this->get_id()) { + $this->get_line_items(); + $this->get_invoice_number(); + } return parent::to_array(); } diff --git a/inc/models/class-product.php b/inc/models/class-product.php index b3dafee25..ddf7218d1 100644 --- a/inc/models/class-product.php +++ b/inc/models/class-product.php @@ -1597,17 +1597,22 @@ public function get_price_variation($duration, $duration_unit) { */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_featured_image_id(); - $this->get_tax_category(); - $this->get_contact_us_label(); - $this->get_contact_us_link(); - $this->get_feature_list(); - $this->get_available_addons(); - $this->get_legacy_options(); - $this->get_pwyw_minimum_amount(); - $this->get_pwyw_suggested_amount(); - $this->get_pwyw_recurring_mode(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values (e.g. false) that can fail validation rules + // like 'integer', preventing the model from being saved at all. + if ($this->get_id()) { + $this->get_featured_image_id(); + $this->get_tax_category(); + $this->get_contact_us_label(); + $this->get_contact_us_link(); + $this->get_feature_list(); + $this->get_available_addons(); + $this->get_legacy_options(); + $this->get_pwyw_minimum_amount(); + $this->get_pwyw_suggested_amount(); + $this->get_pwyw_recurring_mode(); + } return parent::to_array(); } diff --git a/inc/models/class-site.php b/inc/models/class-site.php index 1b06ee4eb..12baa4286 100644 --- a/inc/models/class-site.php +++ b/inc/models/class-site.php @@ -1709,14 +1709,19 @@ function ($replace_list, $from_site_id, $to_site_id) use ($transient) { // phpcs */ public function to_array() { - // Trigger lazy-loading for all meta-backed properties. - $this->get_customer_id(); - $this->get_membership_id(); - $this->get_template_id(); - $this->get_featured_image_id(); - $this->get_categories(); - $this->get_type(); - $this->is_active(); + // Only trigger lazy-loading when the model has been persisted (has an ID). + // For unsaved models (during validate/save), meta is not available and the + // getters return default values (e.g. false) that can fail validation rules + // like 'integer', preventing the model from being saved at all. + if ($this->get_id()) { + $this->get_customer_id(); + $this->get_membership_id(); + $this->get_template_id(); + $this->get_featured_image_id(); + $this->get_categories(); + $this->get_type(); + $this->is_active(); + } $array = parent::to_array();