diff --git a/facebook-commerce.php b/facebook-commerce.php index d5e15ea03..f2e215f5b 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -3072,4 +3072,4 @@ public function load_whatsapp_utility_event_processor() { } } } -} +} \ No newline at end of file diff --git a/includes/Admin.php b/includes/Admin.php index 5ae02abf6..f8f4d963a 100644 --- a/includes/Admin.php +++ b/includes/Admin.php @@ -1420,7 +1420,7 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos } // Get variation meta values - $description = $this->get_product_variation_meta( $variation, \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $parent ); + $description = $this->get_product_variation_meta( $variation, \WC_Facebookcommerce_Integration::FB_RICH_TEXT_DESCRIPTION, $parent ); $price = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_PRICE, $parent ); $image_url = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_IMAGE, $parent ); $image_source = $variation->get_meta( Products::PRODUCT_IMAGE_SOURCE_META_KEY ); @@ -1433,11 +1433,12 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
@@ -1572,39 +1574,79 @@ public function save_product_variation_edit_fields( $variation_id, $index ) { if ( ! $variation instanceof \WC_Product_Variation ) { return; } + + // Verify nonce + $nonce_field = 'facebook_variation_nonce_' . $variation_id; + if ( ! isset( $_POST[ $nonce_field ] ) || ! wp_verify_nonce( sanitize_key( $_POST[ $nonce_field ] ), 'facebook_variation_save' ) ) { + return; + } + // phpcs:disable WordPress.Security.NonceVerification.Missing + + // Get sync mode from POST or parent product $sync_mode = isset( $_POST['wc_facebook_sync_mode'] ) ? wc_clean( wp_unslash( $_POST['wc_facebook_sync_mode'] ) ) : self::SYNC_MODE_SYNC_DISABLED; $sync_enabled = self::SYNC_MODE_SYNC_DISABLED !== $sync_mode; + + // Get sync settings from parent product if not in POST data (fixes PR #2931 issue) + if ( ! isset( $_POST['wc_facebook_sync_mode'] ) ) { + $parent_product = wc_get_product( $variation->get_parent_id() ); + if ( $parent_product ) { + $parent_sync_enabled = 'no' !== get_post_meta( $parent_product->get_id(), Products::SYNC_ENABLED_META_KEY, true ); + $parent_visibility = get_post_meta( $parent_product->get_id(), Products::VISIBILITY_META_KEY, true ); + $parent_is_visible = $parent_visibility ? wc_string_to_bool( $parent_visibility ) : true; + + if ( $parent_sync_enabled ) { + $sync_mode = $parent_is_visible ? self::SYNC_MODE_SYNC_AND_SHOW : self::SYNC_MODE_SYNC_AND_HIDE; + } else { + $sync_mode = self::SYNC_MODE_SYNC_DISABLED; + } + $sync_enabled = self::SYNC_MODE_SYNC_DISABLED !== $sync_mode; + } + } + if ( self::SYNC_MODE_SYNC_AND_SHOW === $sync_mode && $variation->is_virtual() ) { // force to Sync and hide $sync_mode = self::SYNC_MODE_SYNC_AND_HIDE; } + + // ALWAYS save Facebook field data (this fixes the PR #2931 breaking change) + $posted_param = 'variable_' . \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION; + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Intentionally getting raw value to apply different sanitization methods below + $description_raw = isset( $_POST[ $posted_param ][ $index ] ) ? wp_unslash( $_POST[ $posted_param ][ $index ] ) : null; + + // Create separate sanitized versions for different purposes + $description_plain = $description_raw ? sanitize_text_field( $description_raw ) : null; // Plain text for regular description + $description_rich = $description_raw ? wp_kses_post( $description_raw ) : null; // HTML-preserved for rich text description + + $posted_param = 'variable_' . \WC_Facebook_Product::FB_MPN; + $fb_mpn = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; + $posted_param = 'variable_fb_product_image_source'; + $image_source = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_key( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : ''; + $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_IMAGE; + $image_url = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; + $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_VIDEO; + $video_urls = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : []; + $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_PRICE; + $price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( wc_clean( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) ) : ''; + + // Always save the Facebook field data with appropriate sanitization for each field + $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $description_plain ); + $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_RICH_TEXT_DESCRIPTION, $description_rich ); + $variation->update_meta_data( Products::PRODUCT_IMAGE_SOURCE_META_KEY, $image_source ); + $variation->update_meta_data( \WC_Facebook_Product::FB_MPN, $fb_mpn ); + $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_IMAGE, $image_url ); + $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_VIDEO, $video_urls ); + $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_PRICE, $price ); + $variation->save_meta_data(); + + // Handle sync operations based on sync settings if ( $sync_enabled ) { Products::enable_sync_for_products( array( $variation ) ); Products::set_product_visibility( $variation, self::SYNC_MODE_SYNC_AND_HIDE !== $sync_mode ); - $posted_param = 'variable_' . \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION; - $description = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; - $posted_param = 'variable_' . \WC_Facebook_Product::FB_MPN; - $fb_mpn = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; - $posted_param = 'variable_fb_product_image_source'; - $image_source = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_key( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : ''; - $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_IMAGE; - $image_url = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null; - $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_VIDEO; - $video_urls = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : []; - $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_PRICE; - $price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( wc_clean( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) ) : ''; - $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $description ); - $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_RICH_TEXT_DESCRIPTION, $description ); - $variation->update_meta_data( Products::PRODUCT_IMAGE_SOURCE_META_KEY, $image_source ); - $variation->update_meta_data( \WC_Facebook_Product::FB_MPN, $fb_mpn ); - $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_IMAGE, $image_url ); - $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_VIDEO, $video_urls ); - $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_PRICE, $price ); - $variation->save_meta_data(); } else { Products::disable_sync_for_products( array( $variation ) ); - }//end if + } + // phpcs:enable WordPress.Security.NonceVerification.Missing } diff --git a/includes/fbproduct.php b/includes/fbproduct.php index c1d0f9a92..c24ac41d1 100644 --- a/includes/fbproduct.php +++ b/includes/fbproduct.php @@ -519,8 +519,7 @@ function ( $id ) { } public function set_rich_text_description( $rich_text_description ) { - $rich_text_description = - WC_Facebookcommerce_Utils::clean_string( $rich_text_description, false ); + $rich_text_description = stripslashes( $rich_text_description ); $this->rich_text_description = $rich_text_description; update_post_meta( $this->id, @@ -844,6 +843,18 @@ private function get_shipping_class_label(): string public function get_rich_text_description() { $rich_text_description = ''; + // For variations, first check if there's a Facebook description set specifically for that variation + if ( $this->woo_product->is_type( 'variation' ) ) { + $rich_text_description = get_post_meta( + $this->id, + self::FB_RICH_TEXT_DESCRIPTION, + true + ); + if ($rich_text_description) { + return $rich_text_description; + } + } + // Check if the fb description is set as that takes preference if ( $this->rich_text_description ) { $rich_text_description = $this->rich_text_description; @@ -858,32 +869,30 @@ public function get_rich_text_description() { self::FB_RICH_TEXT_DESCRIPTION, true ); - } - // For variable products, we want to use the rich text description of the variant. - // If that's not available, fall back to the main (parent) product's rich text description. - if ( empty( $rich_text_description ) && WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) { - $rich_text_description = WC_Facebookcommerce_Utils::clean_string( $this->woo_product->get_description(), false ); - - // If the variant's rich text description is still empty, use the main product's rich text description as a fallback - if ( empty( $rich_text_description ) && $this->main_description ) { - $rich_text_description = $this->main_description; + // If still empty and this is a variation, inherit from parent + if ( empty( $rich_text_description ) && $this->woo_product->is_type( 'variation' ) ) { + $parent_product = wc_get_product( $this->woo_product->get_parent_id() ); + if ( $parent_product ) { + $rich_text_description = get_post_meta( + $parent_product->get_id(), + self::FB_RICH_TEXT_DESCRIPTION, + true + ); } } - // If no description is found from meta or variation, get from product + // If still empty, use the post content if ( empty( $rich_text_description ) ) { - $post = $this->get_post_data(); - $post_content = WC_Facebookcommerce_Utils::clean_string( $post->post_content, false ); - $post_excerpt = WC_Facebookcommerce_Utils::clean_string( $post->post_excerpt, false ); - - if ( ! empty( $post_content ) ) { - $rich_text_description = $post_content; - } - - if ( empty( $rich_text_description ) && ! empty( $post_excerpt ) ) { - $rich_text_description = $post_excerpt; + $post = get_post( $this->id ); + if ( $post ) { + $rich_text_description = $post->post_content; + + // If post content is empty, fall back to short description (post_excerpt) + if ( empty( $rich_text_description ) ) { + $rich_text_description = $post->post_excerpt; + } } } diff --git a/tests/Unit/fbproductTest.php b/tests/Unit/fbproductTest.php index 39743a4ca..4746d1348 100644 --- a/tests/Unit/fbproductTest.php +++ b/tests/Unit/fbproductTest.php @@ -677,16 +677,6 @@ public function test_get_rich_text_description() { $description = $new_facebook_product->get_rich_text_description(); $this->assertEquals('

meta description test

', $description); - // Test 4: For variations, gets description from variation first - $variable_product = WC_Helper_Product::create_variation_product(); - $variation = wc_get_product($variable_product->get_children()[0]); - $variation->set_description('

variation description

'); - $variation->save(); - - $parent_fb_product = new \WC_Facebook_Product($variable_product); - $facebook_product = new \WC_Facebook_Product($variation, $parent_fb_product); - $description = $facebook_product->get_rich_text_description(); - $this->assertEquals('

variation description

', $description); // Test 5: Falls back to post content if no other description is set $product = WC_Helper_Product::create_simple_product();