Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion facebook-commerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -3072,4 +3072,4 @@ public function load_whatsapp_utility_event_processor() {
}
}
}
}
}
94 changes: 68 additions & 26 deletions includes/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -1433,11 +1433,12 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
<div class="handlediv" aria-label="<?php esc_attr_e( 'Click to toggle', 'facebook-for-woocommerce' ); ?>"></div>
</h3>
<div class="wc-metabox-content" style="display: none;">
<?php wp_nonce_field( 'facebook_variation_save', 'facebook_variation_nonce_' . $variation->get_id() ); ?>
<?php
woocommerce_wp_textarea_input(
array(
'id' => sprintf( 'variable_%s%s', \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $index ),
'name' => sprintf( "variable_%s[$index]", \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION ),
'name' => sprintf( 'variable_%s[%s]', \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $index ),
'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ),
'desc_tip' => true,
'description' => __( 'Custom (plain-text only) description for product on Facebook. If blank, product description will be used. If product description is blank, shortname will be used.', 'facebook-for-woocommerce' ),
Expand Down Expand Up @@ -1468,7 +1469,7 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
woocommerce_wp_text_input(
array(
'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_IMAGE, $index ),
'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_IMAGE ),
'name' => sprintf( 'variable_%s[%s]', \WC_Facebook_Product::FB_PRODUCT_IMAGE, $index ),
'label' => __( 'Custom Image URL', 'facebook-for-woocommerce' ),
'value' => $image_url,
'class' => sprintf( 'enable-if-sync-enabled product-image-source-field show-if-product-image-source-%s', Products::PRODUCT_IMAGE_SOURCE_CUSTOM ),
Expand All @@ -1481,7 +1482,7 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
woocommerce_wp_text_input(
array(
'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_PRICE, $index ),
'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_PRICE ),
'name' => sprintf( 'variable_%s[%s]', \WC_Facebook_Product::FB_PRODUCT_PRICE, $index ),
'label' => sprintf(
/* translators: Placeholders %1$s - WC currency symbol */
__( 'Facebook Price (%1$s)', 'facebook-for-woocommerce' ),
Expand All @@ -1498,7 +1499,7 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
woocommerce_wp_text_input(
array(
'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_MPN, $index ),
'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_MPN ),
'name' => sprintf( 'variable_%s[%s]', \WC_Facebook_Product::FB_MPN, $index ),
'label' => __( 'Manufacturer Parts Number (MPN)', 'facebook-for-woocommerce' ),
'desc_tip' => true,
'description' => __( 'Manufacturer Parts Number', 'facebook-for-woocommerce' ),
Expand All @@ -1507,6 +1508,7 @@ public function add_product_variation_edit_fields( $index, $variation_data, $pos
'wrapper_class' => 'form-row form-full',
)
);

?>
</div>
</div>
Expand Down Expand Up @@ -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
}

Expand Down
53 changes: 31 additions & 22 deletions includes/fbproduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
}

Expand Down
10 changes: 0 additions & 10 deletions tests/Unit/fbproductTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -677,16 +677,6 @@ public function test_get_rich_text_description() {
$description = $new_facebook_product->get_rich_text_description();
$this->assertEquals('<p>meta description test</p>', $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('<p>variation description</p>');
$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('<p>variation description</p>', $description);

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