diff --git a/facebook-commerce-events-tracker.php b/facebook-commerce-events-tracker.php index 2af4c6fac..afc1d55b9 100644 --- a/facebook-commerce-events-tracker.php +++ b/facebook-commerce-events-tracker.php @@ -105,8 +105,10 @@ private function add_hooks() { add_action( 'woocommerce_after_single_product', array( $this, 'inject_view_content_event' ) ); add_action( 'woocommerce_after_single_product', array( $this, 'maybe_inject_search_event' ) ); + // ViewCategory events add_action( 'woocommerce_after_shop_loop', array( $this, 'inject_view_category_event' ) ); + // Search events add_action( 'pre_get_posts', array( $this, 'inject_search_event' ) ); add_filter( 'woocommerce_redirect_single_search_result', array( $this, 'maybe_add_product_search_event_to_session' ) ); @@ -122,23 +124,23 @@ private function add_hooks() { // InitiateCheckout events add_action( 'woocommerce_after_checkout_form', array( $this, 'inject_initiate_checkout_event' ) ); + // InitiateCheckout events for checkout block. add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'inject_initiate_checkout_event' ) ); + // Purchase and Subscribe events - add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'inject_purchase_event' ) ); + add_action( 'woocommerce_new_order', array( $this, 'inject_purchase_event' ) ); + add_action( 'woocommerce_payment_complete', array( $this, 'inject_purchase_event' ), 10 ); + add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'inject_purchase_event' ), 20 ); add_action( 'woocommerce_thankyou', array( $this, 'inject_purchase_event' ), 40 ); + add_action( 'woocommerce_order_status_processing', array( $this, 'inject_purchase_event' ), 50 ); + add_action( 'woocommerce_order_status_completed', array( $this, 'inject_purchase_event' ), 50 ); + add_action( 'woocommerce_process_shop_order_meta', array( $this, 'inject_purchase_event' ), 60 ); - // Checkout update order meta from the Checkout Block. - if ( version_compare( \Automattic\WooCommerce\Blocks\Package::get_version(), '7.2.0', '>=' ) ) { - add_action( 'woocommerce_store_api_checkout_update_order_meta', array( $this, 'inject_order_meta_event_for_checkout_block_flow' ), 10, 1 ); - } elseif ( version_compare( \Automattic\WooCommerce\Blocks\Package::get_version(), '6.3.0', '>=' ) ) { - add_action( 'woocommerce_blocks_checkout_update_order_meta', array( $this, 'inject_order_meta_event_for_checkout_block_flow' ), 10, 1 ); - } else { - add_action( '__experimental_woocommerce_blocks_checkout_update_order_meta', array( $this, 'inject_order_meta_event_for_checkout_block_flow' ), 10, 1 ); - } - - // TODO move this in some 3rd party plugin integrations handler at some point {FN 2020-03-20} + // Lead events through Contact Form 7 add_action( 'wpcf7_contact_form', array( $this, 'inject_lead_event_hook' ), 11 ); + + // Flush pending events on shutdown add_action( 'shutdown', array( $this, 'send_pending_events' ) ); } @@ -795,6 +797,8 @@ public function inject_initiate_checkout_event() { * * This may happen either when: * - WooCommerce signals a payment transaction complete (most gateways) + * - The order status is changed through the Woo dashboard to Processing or Completed + * - The Payment Completed event is fired, which happens in case of some external payment gateways. * - Customer reaches Thank You page skipping payment (for gateways that do not require payment, e.g. Cheque, BACS, Cash on delivery...) * * The method checks if the event was not triggered already avoiding a duplicate. @@ -808,7 +812,9 @@ public function inject_purchase_event( $order_id ) { $event_name = 'Purchase'; - if ( ! $this->is_pixel_enabled() || $this->pixel->is_last_event( $event_name ) ) { + $valid_purchase_order_states = array( 'processing', 'completed' ); + + if ( ! $this->is_pixel_enabled() ) { return; } @@ -817,23 +823,26 @@ public function inject_purchase_event( $order_id ) { if ( ! $order ) { return; } + + // Get the status of the order to ensure we track the actual purchases and not the ones that have a failed payment. + $order_state = $order->get_status(); - // use a session flag to ensure an order is tracked with any payment method, also when the order is placed through AJAX - $order_placed_flag = '_wc_' . facebook_for_woocommerce()->get_id() . '_order_placed_' . $order_id; - - // use a session flag to ensure a Purchase event is not tracked multiple times + // use a session flag to ensure this Purchase event is not tracked multiple times $purchase_tracked_flag = '_wc_' . facebook_for_woocommerce()->get_id() . '_purchase_tracked_' . $order_id; - // when saving the order meta data: add a flag to mark the order tracked - if ( 'woocommerce_checkout_update_order_meta' === current_action() ) { - set_transient( $order_placed_flag, 'yes', 15 * MINUTE_IN_SECONDS ); + // Return if this Purchase event has already been tracked + if ( 'yes' === get_transient( $purchase_tracked_flag ) || $order->meta_exists( '_meta_purchase_tracked' ) || ! in_array( $order_state, $valid_purchase_order_states ) ) { return; } - // bail if by the time we are on the thank you page the meta has not been set or we already tracked a Purchase event - if ( 'yes' !== get_transient( $order_placed_flag ) || 'yes' === get_transient( $purchase_tracked_flag ) ) { - return; - } + // Mark the order as tracked for the session + set_transient( $purchase_tracked_flag, 'yes', 15 * MINUTE_IN_SECONDS ); + + // Set a flag to ensure this Purchase event is not going to be sent across different sessions + $order->add_meta_data( '_meta_purchase_tracked', true, true ); + + // Save the metadata + $order->save(); $content_type = 'product'; $contents = array(); @@ -885,41 +894,6 @@ public function inject_purchase_event( $order_id ) { $this->inject_subscribe_event( $order_id ); - // mark the order as tracked - set_transient( $purchase_tracked_flag, 'yes', 15 * MINUTE_IN_SECONDS ); - - } - - /** - * Inject order meta gor WooCommerce Checkout Blocks flow. - * The blocks flow does not trigger the woocommerce_checkout_update_order_meta so we can't rely on it. - * The Checkout Block has its own hook that allows us to inject the meta at - * the appropriate moment: woocommerce_store_api_checkout_update_order_meta. - * - * Note: __experimental_woocommerce_blocks_checkout_update_order_meta has been deprecated - * as of WooCommerce Blocks 6.3.0 - * - * @since 2.6.6 - * - * @param WC_Order|int $the_order Order object or id. - */ - public function inject_order_meta_event_for_checkout_block_flow( $the_order ) { - - $event_name = 'Purchase'; - - if ( ! $this->is_pixel_enabled() || $this->pixel->is_last_event( $event_name ) ) { - return; - } - - $order = wc_get_order($the_order); - - if ( ! $order ) { - return; - } - - $order_placed_flag = '_wc_' . facebook_for_woocommerce()->get_id() . '_order_placed_' . $order->get_id(); - set_transient( $order_placed_flag, 'yes', 15 * MINUTE_IN_SECONDS ); - } diff --git a/facebook-commerce.php b/facebook-commerce.php index 80d79e2f8..3e732db13 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -855,27 +855,27 @@ private function save_facebook_product_attributes( $woo_product ) { if ( isset( $_POST[ WC_Facebook_Product::FB_SIZE ] ) ) { $woo_product->set_fb_size( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_SIZE ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_COLOR ] ) ) { $woo_product->set_fb_color( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_COLOR ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_MATERIAL ] ) ) { $woo_product->set_fb_material( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_MATERIAL ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_PATTERN ] ) ) { $woo_product->set_fb_pattern( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PATTERN ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_AGE_GROUP ] ) ) { $woo_product->set_fb_age_group( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_AGE_GROUP ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_GENDER ] ) ) { $woo_product->set_fb_gender( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_GENDER ] ) ) ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_CONDITION ] ) ) { $woo_product->set_fb_condition( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PRODUCT_CONDITION ] ) ) ); } @@ -907,7 +907,7 @@ private function save_product_settings( WC_Product $product ) { $woo_product->set_description( sanitize_text_field( wp_unslash( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) ); $woo_product->set_rich_text_description( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ); } - + if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) { $woo_product->set_price( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) ); } @@ -2695,17 +2695,6 @@ public function is_legacy_feed_file_generation_enabled() { return 'yes' === get_option( self::OPTION_LEGACY_FEED_FILE_GENERATION_ENABLED, 'yes' ); } - /** - * Determines whether meta diagnosis is enabled. - * - * @return bool - * @since 3.4.4 - * - */ - public function is_meta_diagnosis_enabled() { - return (bool) ( 'yes' === get_option( self::SETTING_ENABLE_META_DIAGNOSIS ) ); - } - /** * Determines whether debug mode is enabled. * diff --git a/includes/Admin/Settings_Screens/Connection.php b/includes/Admin/Settings_Screens/Connection.php index 14ad3ada1..64bbdf5df 100644 --- a/includes/Admin/Settings_Screens/Connection.php +++ b/includes/Admin/Settings_Screens/Connection.php @@ -347,18 +347,6 @@ public function get_settings() { 'default' => 'no', ), - array( - 'id' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_NEW_STYLE_FEED_GENERATOR, - 'title' => __( 'Experimental! Enable new style feed generation', 'facebook-for-woocommerce' ), - 'type' => 'checkbox', - 'desc' => __( 'Use new, memory improved, feed generation process.', 'facebook-for-woocommerce' ), - /** - * Translators: %s URL to the documentation page. - */ - 'desc_tip' => sprintf( __( 'This is an experimental feature in testing phase. Only enable this if you are experiencing problems with feed generation. Learn more.', 'facebook-for-woocommerce' ), 'https://woocommerce.com/document/facebook-for-woocommerce/#feed-generation' ), - 'default' => 'no', - ), - array( 'type' => 'sectionend' ), ); diff --git a/includes/Admin/Settings_Screens/Product_Sync.php b/includes/Admin/Settings_Screens/Product_Sync.php index 8dc50e3fb..9efcc65ef 100644 --- a/includes/Admin/Settings_Screens/Product_Sync.php +++ b/includes/Admin/Settings_Screens/Product_Sync.php @@ -304,18 +304,6 @@ public function get_settings() { ), ), - array( - 'id' => \WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE, - 'title' => __( 'Product description sync', 'facebook-for-woocommerce' ), - 'type' => 'select', - 'class' => 'product-sync-field', - 'desc_tip' => __( 'Choose which product description to display in the Facebook catalog.', 'facebook-for-woocommerce' ), - 'default' => \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_STANDARD, - 'options' => array( - \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_STANDARD => __( 'Standard description', 'facebook-for-woocommerce' ), - \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT => __( 'Short description', 'facebook-for-woocommerce' ), - ), - ), array( 'id' => Commerce::OPTION_GOOGLE_PRODUCT_CATEGORY_ID, 'type' => 'product_sync_google_product_categories', diff --git a/includes/Events/AAMSettings.php b/includes/Events/AAMSettings.php index 58cee4346..817624e4d 100644 --- a/includes/Events/AAMSettings.php +++ b/includes/Events/AAMSettings.php @@ -160,7 +160,7 @@ public function set_pixel_id( $pixel_id ) { * @return string */ public function __toString() { - return json_encode( + return wp_json_encode( array( 'enableAutomaticMatching' => $this->enable_automatic_matching, 'enabledAutomaticMatchingFields' => $this->enabled_automatic_matching_fields, diff --git a/includes/Lifecycle.php b/includes/Lifecycle.php index 297774cdc..ed490e14d 100644 --- a/includes/Lifecycle.php +++ b/includes/Lifecycle.php @@ -149,10 +149,6 @@ private function migrate_1_9_settings() { $new_settings[ \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC ] = $product_sync_enabled ? 'yes' : 'no'; } - if ( ! isset( $new_settings[ \WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE ] ) ) { - $new_settings[ \WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE ] = ! empty( get_option( 'fb_sync_short_description', 0 ) ) ? \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT : \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_STANDARD; - } - if ( ! isset( $new_settings[ \WC_Facebookcommerce_Integration::SETTING_SCHEDULED_RESYNC_OFFSET ] ) ) { $autosync_time = get_option( 'woocommerce_fb_autosync_time' ); $parsed_time = ! empty( $autosync_time ) ? strtotime( $autosync_time ) : false; @@ -219,7 +215,6 @@ protected function upgrade_to_2_0_0() { 'enable_product_sync' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC, 'excluded_product_category_ids' => \WC_Facebookcommerce_Integration::SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS, 'excluded_product_tag_ids' => \WC_Facebookcommerce_Integration::SETTING_EXCLUDED_PRODUCT_TAG_IDS, - 'product_description_mode' => \WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE, 'enable_messenger' => self::SETTING_ENABLE_MESSENGER, 'messenger_locale' => self::SETTING_MESSENGER_LOCALE, 'messenger_greeting' => self::SETTING_MESSENGER_GREETING, diff --git a/includes/fbproduct.php b/includes/fbproduct.php index 9c70685f0..7b729f835 100644 --- a/includes/fbproduct.php +++ b/includes/fbproduct.php @@ -78,6 +78,7 @@ class WC_Facebook_Product { // Should match facebook-commerce.php while we migrate that code over // to this object. const FB_PRODUCT_DESCRIPTION = 'fb_product_description'; + const FB_SHORT_DESCRIPTION = 'fb_product_short_description'; const FB_PRODUCT_PRICE = 'fb_product_price'; const FB_SIZE = 'fb_size'; const FB_COLOR = 'fb_color'; @@ -152,11 +153,6 @@ class WC_Facebook_Product { */ private $main_description; - /** - * @var bool Sync short description. - */ - private $sync_short_description; - /** * @var bool Product visibility on Facebook. */ @@ -239,7 +235,6 @@ public function __construct( $wpid, $parent_product = null ) { $this->gallery_urls = null; $this->fb_use_parent_image = null; $this->main_description = ''; - $this->sync_short_description = \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT === facebook_for_woocommerce()->get_integration()->get_product_description_mode(); $this->rich_text_description = ''; if ( $meta = get_post_meta( $this->id, self::FB_VISIBILITY, true ) ) { @@ -268,7 +263,7 @@ public function __construct( $wpid, $parent_product = null ) { */ public function __get( $key ) { // Add warning for private properties. - if ( in_array( $key, array( 'fb_description', 'gallery_urls', 'fb_use_parent_image', 'main_description', 'sync_short_description' ), true ) ) { + if ( in_array( $key, array( 'fb_description', 'gallery_urls', 'fb_use_parent_image', 'main_description' ), true ) ) { /* translators: %s property name. */ _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'The %s property is private and should not be accessed outside its class.', 'facebook-for-woocommerce' ), esc_html( $key ) ), '3.0.32' ); return $this->$key; @@ -709,7 +704,7 @@ public function get_fb_description() { $description = $post_content; } - if ( $this->sync_short_description || ( empty( $description ) && ! empty( $post_excerpt ) ) ) { + if ( empty( $description ) && ! empty( $post_excerpt ) ) { $description = $post_excerpt; } @@ -728,6 +723,72 @@ public function get_fb_description() { return apply_filters( 'facebook_for_woocommerce_fb_product_description', $description, $this->id ); } + /** + * Get the short description for a product. + * + * This function retrieves the short product description, but unlike the main description + * it should only use values specifically set for short description. + * + * @return string The short description for the product. + */ + public function get_fb_short_description() { + $short_description = ''; + + // For variations, first try to get the short description from the parent product + if (WC_Facebookcommerce_Utils::is_variation_type($this->woo_product->get_type())) { + // Get the parent product + $parent_id = $this->woo_product->get_parent_id(); + if ($parent_id) { + $parent_post = get_post($parent_id); + if ($parent_post && !empty($parent_post->post_excerpt)) { + $short_description = WC_Facebookcommerce_Utils::clean_string($parent_post->post_excerpt); + } + } + + // If no parent description found, try getting the variation's own excerpt + if (empty($short_description)) { + $post = $this->get_post_data(); + if ($post && !empty($post->post_excerpt)) { + $short_description = WC_Facebookcommerce_Utils::clean_string($post->post_excerpt); + } + } + + // If still no short description, check if main description is short enough + if (empty($short_description)) { + $main_description = WC_Facebookcommerce_Utils::clean_string($this->woo_product->get_description()); + if (!empty($main_description) && strlen($main_description) <= 1000) { + $short_description = $main_description; + } + } + + return apply_filters('facebook_for_woocommerce_fb_product_short_description', $short_description, $this->id); + } + + // Use the product's short description (excerpt) from WooCommerce + $post = $this->get_post_data(); + $post_excerpt = WC_Facebookcommerce_Utils::clean_string($post->post_excerpt); + + if (!empty($post_excerpt)) { + $short_description = $post_excerpt; + } + + // If no short description (excerpt) found, check if main description is short enough + if (empty($short_description)) { + $post_content = WC_Facebookcommerce_Utils::clean_string($post->post_content); + if (!empty($post_content) && strlen($post_content) <= 1000) { + $short_description = $post_content; + } + } + + /** + * Filters the FB product short description. + * + * @param string $short_description Facebook product short description. + * @param int $id WooCommerce Product ID. + */ + return apply_filters('facebook_for_woocommerce_fb_product_short_description', $short_description, $this->id); + } + /** * Get the rich text description for a product. * @@ -779,7 +840,7 @@ public function get_rich_text_description() { $rich_text_description = $post_content; } - if ( $this->sync_short_description || ( empty( $rich_text_description ) && ! empty( $post_excerpt ) ) ) { + if ( empty( $rich_text_description ) && ! empty( $post_excerpt ) ) { $rich_text_description = $post_excerpt; } } @@ -1250,6 +1311,7 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel $product_data = array(); $product_data[ 'description' ] = Helper::str_truncate( $this->get_fb_description(), self::MAX_DESCRIPTION_LENGTH ); + $product_data[ 'short_description' ] = $this->get_fb_short_description(); $product_data[ 'rich_text_description' ] = $this->get_rich_text_description(); $product_data[ 'product_type' ] = $categories['categories']; $product_data[ 'brand' ] = Helper::str_truncate( $this->get_fb_brand(), 100 ); @@ -1265,8 +1327,8 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel $product_data[ 'age_group' ] = $this->get_fb_age_group(); $product_data[ 'gender' ] = $this->get_fb_gender(); $product_data[ 'material' ] = Helper::str_truncate( $this->get_fb_material(), 100 ); - $product_data[ 'woo_product_type' ] = $this->get_type(); - $product_data[ 'unmapped_attributes' ] = $this->get_unmapped_attributes(); + // $product_data[ 'woo_product_type' ] = $this->get_type(); + // $product_data[ 'unmapped_attributes' ] = $this->get_unmapped_attributes(); if ( self::PRODUCT_PREP_TYPE_ITEMS_BATCH === $type_to_prepare_for ) { $product_data['title'] = Helper::str_truncate( WC_Facebookcommerce_Utils::clean_string( $this->get_title() ), self::MAX_TITLE_LENGTH ); diff --git a/tests/Unit/Admin/Settings/ConnectionTest.php b/tests/Unit/Admin/Settings/ConnectionTest.php index 18a94e4d2..4dee0de6d 100644 --- a/tests/Unit/Admin/Settings/ConnectionTest.php +++ b/tests/Unit/Admin/Settings/ConnectionTest.php @@ -18,34 +18,29 @@ public function testEnqueueAssetsWhenNotOnPage(): void { $connection = $this->getMockBuilder(Connection::class) ->onlyMethods(['is_current_screen_page']) ->getMock(); - + $connection->method('is_current_screen_page') ->willReturn(false); - + // No styles should be enqueued $connection->enqueue_assets(); - + $this->assertFalse(wp_style_is('wc-facebook-admin-connection-settings')); } public function testGetSettings(): void { $settings = $this->connection->get_settings(); - + $this->assertIsArray($settings); $this->assertNotEmpty($settings); - + // Check that the settings array has the expected structure $this->assertArrayHasKey('type', $settings[0]); $this->assertEquals('title', $settings[0]['type']); - + // Check debug mode setting $debug_setting = $settings[1]; $this->assertEquals('checkbox', $debug_setting['type']); $this->assertEquals('no', $debug_setting['default']); - - // Check feed generator setting - $feed_setting = $settings[2]; - $this->assertEquals('checkbox', $feed_setting['type']); - $this->assertEquals('no', $feed_setting['default']); } } diff --git a/tests/Unit/fbproductTest.php b/tests/Unit/fbproductTest.php index 09747b566..566ed4b8f 100644 --- a/tests/Unit/fbproductTest.php +++ b/tests/Unit/fbproductTest.php @@ -93,15 +93,6 @@ public function test_get_fb_description_from_post_content() { $description = $facebook_product->get_fb_description(); $this->assertEquals( $description, get_post( $product->get_id() )->post_content ); - // Gets description from excerpt ignoring content when short mode is set - add_option( - WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE, - WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT - ); - - $facebook_product = new \WC_Facebook_Product( $product ); - $description = $facebook_product->get_fb_description(); - $this->assertEquals( $description, get_post( $product->get_id() )->post_excerpt ); } /** @@ -534,7 +525,7 @@ public function test_prepare_product_with_video_field() { update_post_meta($this->product->get_id(), WC_Facebook_Product::FB_PRODUCT_VIDEO, $video_urls); $product_data = $this->fb_product->prepare_product(null, WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH); - + $this->assertArrayHasKey('video', $product_data); $this->assertEquals($expected_video_urls, $product_data['video']); } @@ -542,13 +533,13 @@ public function test_prepare_product_with_video_field() { public function test_set_product_video_urls() { // Prepare attachment IDs $attachment_ids = '123,456'; - + // Mock get_video_urls_from_attachment_ids function $this->fb_product = $this->getMockBuilder(WC_Facebook_Product::class) ->setConstructorArgs([$this->product]) ->setMethods(['get_video_urls_from_attachment_ids']) ->getMock(); - + $this->fb_product->method('get_video_urls_from_attachment_ids') ->willReturnCallback(function($id) { switch ($id) { @@ -560,14 +551,14 @@ public function test_set_product_video_urls() { return ''; } }); - + // Set the video URLs in post meta $video_urls = array_filter(array_map([$this->fb_product, 'get_video_urls_from_attachment_ids'], explode(',', $attachment_ids))); update_post_meta( $this->fb_product->get_id(), WC_Facebook_Product::FB_PRODUCT_VIDEO, $video_urls ); - + // Get the saved video URLs from post meta $saved_video_urls = get_post_meta( $this->product->get_id(), WC_Facebook_Product::FB_PRODUCT_VIDEO, true ); - + // Assert that the saved video URLs match the expected values $this->assertEquals( $saved_video_urls, $video_urls); @@ -595,7 +586,7 @@ public function test_prepare_product_items_batch() { $this->assertArrayHasKey('image_link', $product_data); } - + /** * Test it gets rich text description from post meta. * @return void @@ -608,8 +599,8 @@ public function test_get_rich_text_description_from_post_meta() { $rich_text_description = $facebook_product->get_rich_text_description(); $this->assertEquals( $rich_text_description, 'rich text description' ); - } - + } + /** * Tests for get_rich_text_description() method */ @@ -618,7 +609,7 @@ public function test_get_rich_text_description() { $product = WC_Helper_Product::create_simple_product(); $facebook_product = new \WC_Facebook_Product($product); $facebook_product->set_description('fb description test'); - + $description = $facebook_product->get_rich_text_description(); $this->assertEquals('fb description test', $description); @@ -638,7 +629,7 @@ public function test_get_rich_text_description() { $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(); @@ -648,21 +639,15 @@ public function test_get_rich_text_description() { $product = WC_Helper_Product::create_simple_product(); $product->set_description('

product content description

'); $product->save(); - + $facebook_product = new \WC_Facebook_Product($product); $description = $facebook_product->get_rich_text_description(); $this->assertEquals('

product content description

', $description); - // Test 6: Falls back to post excerpt if content is empty and sync_short_description is true - add_option( - WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE, - WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT - ); - $product->set_description(''); $product->set_short_description('

short description test

'); $product->save(); - + $facebook_product = new \WC_Facebook_Product($product); $description = $facebook_product->get_rich_text_description(); $this->assertEquals('

short description test

', $description); @@ -671,10 +656,10 @@ public function test_get_rich_text_description() { add_filter('facebook_for_woocommerce_fb_rich_text_description', function($description) { return '

filtered description

'; }); - + $description = $facebook_product->get_rich_text_description(); $this->assertEquals('

filtered description

', $description); - + // Cleanup remove_all_filters('facebook_for_woocommerce_fb_rich_text_description'); delete_option(WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE); @@ -710,7 +695,7 @@ public function test_rich_text_description_html_preservation() { $facebook_product->set_rich_text_description($html_content); $description = $facebook_product->get_rich_text_description(); - + // Test HTML structure is preserved $this->assertStringContainsString('
', $description); $this->assertStringContainsString('

', $description); @@ -729,15 +714,15 @@ public function test_rich_text_description_html_preservation() { public function test_empty_rich_text_description_fallback() { $product = WC_Helper_Product::create_simple_product(); $facebook_product = new \WC_Facebook_Product($product); - + // Ensure rich_text_description is empty $facebook_product->set_rich_text_description(''); - + // Test fallback to post meta update_post_meta($product->get_id(), \WC_Facebook_Product::FB_RICH_TEXT_DESCRIPTION, '

fallback description

'); $description = $facebook_product->get_rich_text_description(); $this->assertEquals('

fallback description

', $description); - + // Test behavior when both rich_text_description and post meta are empty delete_post_meta($product->get_id(), \WC_Facebook_Product::FB_RICH_TEXT_DESCRIPTION); $description = $facebook_product->get_rich_text_description(); @@ -751,36 +736,36 @@ public function test_rich_text_description_variants() { // Create variable product with variation $variable_product = WC_Helper_Product::create_variation_product(); $variation = wc_get_product($variable_product->get_children()[0]); - + // Set up parent product $parent_fb_product = new \WC_Facebook_Product($variable_product); - + // Set the rich text description using post meta for the parent update_post_meta($variable_product->get_id(), \WC_Facebook_Product::FB_RICH_TEXT_DESCRIPTION, '

parent rich text

'); - + // Test 1: Variation inherits parent's rich text description when empty $facebook_product = new \WC_Facebook_Product($variation, $parent_fb_product); $description = $facebook_product->get_rich_text_description(); $this->assertEquals('

parent rich text

', $description); - + // Test 2: Variation uses its own rich text description when set $variation_fb_product = new \WC_Facebook_Product($variation, $parent_fb_product); $variation_fb_product->set_rich_text_description('

variation rich text

'); $description = $variation_fb_product->get_rich_text_description(); $this->assertEquals('

variation rich text

', $description); - + // // Test 3: Variation uses its post meta when set // update_post_meta($variation->get_id(), \WC_Facebook_Product::FB_RICH_TEXT_DESCRIPTION, '

variation meta rich text

'); // $new_variation_product = new \WC_Facebook_Product($variation, $parent_fb_product); // $description = $new_variation_product->get_rich_text_description(); // $this->assertEquals('

variation meta rich text

', $description); - + // Test 4: Fallback chain for variations delete_post_meta($variation->get_id(), \WC_Facebook_Product::FB_RICH_TEXT_DESCRIPTION); } /** - * Test Brand is added for simple product + * Test Brand is added for simple product * @return void */ public function test_brand_for_simple_product_set() { @@ -796,7 +781,7 @@ public function test_brand_for_simple_product_set() { } /** - * Test MPN is added for simple product + * Test MPN is added for simple product * @return void */ public function test_mpn_for_simple_product_set() { @@ -812,7 +797,7 @@ public function test_mpn_for_simple_product_set() { } /** - * Test MPN is added for variable product + * Test MPN is added for variable product * @return void */ public function test_mpn_for_variable_product_set() { @@ -836,10 +821,10 @@ public function test_get_fb_brand_variable_products() { // Create a variable product and set the brand for the parent $variable_product = WC_Helper_Product::create_variation_product(); $facebook_product_parent = new \WC_Facebook_Product($variable_product); - + // Set brand for parent product update_post_meta($variable_product->get_id(), \WC_Facebook_Product::FB_BRAND, 'Nike'); - + // Get the variation product $variation = wc_get_product($variable_product->get_children()[0]); @@ -867,7 +852,7 @@ public function test_get_fb_brand_variable_products() { private function create_product_attribute($name, $value, $is_taxonomy) { $attribute = new \WC_Product_Attribute(); $attribute->set_id(0); - + // Handle attribute names with spaces if ($is_taxonomy) { $name = strtolower(str_replace(' ', '-', $name)); @@ -875,15 +860,15 @@ private function create_product_attribute($name, $value, $is_taxonomy) { } else { $attribute->set_name($name); } - + if ($is_taxonomy) { // For taxonomy attributes $values = is_array($value) ? $value : [$value]; $term_ids = []; - + foreach ($values as $term_value) { $taxonomy = $attribute->get_name(); - + // Create the taxonomy if it doesn't exist if (!taxonomy_exists($taxonomy)) { register_taxonomy( @@ -897,7 +882,7 @@ private function create_product_attribute($name, $value, $is_taxonomy) { ] ); } - + // Create and get the term $term = wp_insert_term($term_value, $taxonomy); if (!is_wp_error($term)) { @@ -912,11 +897,11 @@ private function create_product_attribute($name, $value, $is_taxonomy) { $attribute->set_options($values); $attribute->is_taxonomy(false); } - + $attribute->set_position(0); $attribute->set_visible(1); $attribute->set_variation(0); - + return $attribute; } @@ -934,7 +919,7 @@ private function process_attributes_and_verify($product, $input_attributes, $exp ); $attributes[] = $attribute; } - + $product->set_attributes($attributes); $product->save(); @@ -968,14 +953,14 @@ private function verify_saved_meta_values($product_id, $expected_output) { foreach ($meta_key_map as $field => $meta_key) { $saved_value = get_post_meta($product_id, $meta_key, true); - + if (!empty($expected_output[$field])) { // Get term name if it's a taxonomy term ID if (is_numeric($saved_value)) { $term = get_term($saved_value); $saved_value = $term ? $term->name : $saved_value; } - + $this->assertEquals( $expected_output[$field], $saved_value, @@ -1056,59 +1041,128 @@ public function test_external_update_time_unset() { $this->assertEquals(isset($data['external_update_time']), false); } + /** + * Test fallback to main description when it's less than 1000 characters. + */ + public function test_get_fb_short_description_fallback_to_short_main_description() { + // Arrange + $product = WC_Helper_Product::create_simple_product(); + $short_description = 'Short main description'; + // Set up the test conditions + $product->set_description($short_description); + $product->set_short_description(''); // Ensure short description is empty + $product->save(); + + // Act + $facebook_product = new \WC_Facebook_Product($product); + $result_description = $facebook_product->get_fb_short_description(); + + // Assert + $this->assertEquals( + $short_description, + $result_description, + 'Short main description should be used when excerpt is empty' + ); + } /** - * Tests for get_fb_short_description() method + * Test fallback to main description when it's exactly 1000 characters. */ - public function test_get_fb_short_description() { - // Test 1: Variation products should inherit parent's short description - $variable_product = WC_Helper_Product::create_variation_product(); - $variation = wc_get_product($variable_product->get_children()[0]); - - // Set the parent product's short description - $variable_product->set_short_description('parent short description'); - $variable_product->save(); - - // Even if we try to set a short description on the variation (which we dont have functionality for in WooCommerce UI) - $variation->set_short_description('variation short description - should be ignored'); - $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_fb_short_description(); - - // Variations should inherit the parent product's short description - $this->assertEquals('parent short description', $description, 'Variations should inherit parent short description'); - - // Test 2: Gets short description from post excerpt for simple products + public function test_get_fb_short_description_fallback_to_exact_length_main_description() { + // Arrange $product = WC_Helper_Product::create_simple_product(); - $product->set_short_description('product short description'); - $product->save(); + $exact_length_description = str_repeat('a', 1000); + // Set up the test conditions + $product->set_description($exact_length_description); + $product->set_short_description(''); // Ensure short description is empty + $product->save(); + + // Act $facebook_product = new \WC_Facebook_Product($product); - $description = $facebook_product->get_fb_short_description(); - $this->assertEquals('product short description', $description); - - // Test 3: Returns empty string when no short description exists + $result_description = $facebook_product->get_fb_short_description(); + + // Assert + $this->assertEquals( + $exact_length_description, + $result_description, + 'Main description of exactly 1000 characters should be used when excerpt is empty' + ); + $this->assertEquals( + 1000, + strlen($result_description), + 'Result description should be exactly 1000 characters long' + ); + } + + /** + * Test that main description is not used when it exceeds 1000 characters. + */ + public function test_get_fb_short_description_no_fallback_to_long_main_description() { + // Arrange $product = WC_Helper_Product::create_simple_product(); - $product->set_short_description(''); - $product->save(); + $too_long_description = str_repeat('a', 1001); + // Set up the test conditions + $product->set_description($too_long_description); + $product->set_short_description(''); // Ensure short description is empty + $product->save(); + + // Act $facebook_product = new \WC_Facebook_Product($product); - $description = $facebook_product->get_fb_short_description(); - $this->assertEquals('', $description); - - // Test 4: Applies filters - $filter = $this->add_filter_with_safe_teardown('facebook_for_woocommerce_fb_product_short_description', function($description, $id) { - return 'filtered short description for product ' . $id; - }, 10, 2); + $result_description = $facebook_product->get_fb_short_description(); + + // Assert + $this->assertEquals( + '', + $result_description, + 'Should not fallback to main description if it exceeds 1000 characters' + ); + $this->assertNotEquals( + $too_long_description, + $result_description, + 'Long description should not be used even if no other description is available' + ); + $this->assertLessThan( + strlen($too_long_description), + strlen($result_description), + 'Result description should be shorter than the too-long main description' + ); + } + + /** + * Test that short description is always preferred over main description, even when both are present + * and main description is short enough to be used. + */ + public function test_get_fb_short_description_prefers_short_over_main() { + // Arrange + $product = WC_Helper_Product::create_simple_product(); + $short_description = 'Short product description'; + $main_description = 'Main product description that is also short'; - $description = $facebook_product->get_fb_short_description(); - $this->assertEquals('filtered short description for product ' . $product->get_id(), $description); + // Set up both descriptions + $product->set_description($main_description); + $product->set_short_description($short_description); + $product->save(); + + // Act + $facebook_product = new \WC_Facebook_Product($product); + $result_description = $facebook_product->get_fb_short_description(); + + // Assert + $this->assertEquals( + $short_description, + $result_description, + 'Short description should be used when available, regardless of main description length' + ); - // Remove the filter early - $filter->teardown_safely_immediately(); + // Verify we're not using the main description + $this->assertNotEquals( + $main_description, + $result_description, + 'Main description should not be used when short description is available' + ); } /** @@ -1117,7 +1171,7 @@ public function test_get_fb_short_description() { public function test_get_unmapped_attributes_no_attributes() { $product = WC_Helper_Product::create_simple_product(); $facebook_product = new \WC_Facebook_Product($product); - + $unmapped_attributes = $facebook_product->get_unmapped_attributes(); $this->assertIsArray($unmapped_attributes); $this->assertEmpty($unmapped_attributes); @@ -1128,7 +1182,7 @@ public function test_get_unmapped_attributes_no_attributes() { */ public function test_get_unmapped_attributes_only_mapped() { $product = WC_Helper_Product::create_simple_product(); - + // Add mapped attributes (size, color) $attributes = array(); $attributes[] = $this->create_product_attribute('size', 'Large', false); @@ -1138,7 +1192,7 @@ public function test_get_unmapped_attributes_only_mapped() { $facebook_product = new \WC_Facebook_Product($product); $unmapped_attributes = $facebook_product->get_unmapped_attributes(); - + $this->assertIsArray($unmapped_attributes); $this->assertEmpty($unmapped_attributes); } @@ -1148,7 +1202,7 @@ public function test_get_unmapped_attributes_only_mapped() { */ public function test_get_unmapped_attributes_only_unmapped() { $product = WC_Helper_Product::create_simple_product(); - + // Add unmapped attributes $attributes = array(); $attributes[] = $this->create_product_attribute('weight', '2kg', false); @@ -1158,14 +1212,14 @@ public function test_get_unmapped_attributes_only_unmapped() { $facebook_product = new \WC_Facebook_Product($product); $unmapped_attributes = $facebook_product->get_unmapped_attributes(); - + $this->assertIsArray($unmapped_attributes); $this->assertCount(2, $unmapped_attributes); - + // Verify first unmapped attribute $this->assertEquals('weight', $unmapped_attributes[0]['name']); $this->assertEquals('2kg', $unmapped_attributes[0]['value']); - + // Verify second unmapped attribute $this->assertEquals('style', $unmapped_attributes[1]['name']); $this->assertEquals('Modern', $unmapped_attributes[1]['value']); @@ -1176,7 +1230,7 @@ public function test_get_unmapped_attributes_only_unmapped() { */ public function test_get_unmapped_attributes_mixed() { $product = WC_Helper_Product::create_simple_product(); - + // Add both mapped and unmapped attributes $attributes = array(); $attributes[] = $this->create_product_attribute('size', 'Medium', false); // mapped @@ -1188,10 +1242,10 @@ public function test_get_unmapped_attributes_mixed() { $facebook_product = new \WC_Facebook_Product($product); $unmapped_attributes = $facebook_product->get_unmapped_attributes(); - + $this->assertIsArray($unmapped_attributes); $this->assertCount(2, $unmapped_attributes); - + // Verify only unmapped attributes are returned $this->assertEquals('weight', $unmapped_attributes[0]['name']); $this->assertEquals('3kg', $unmapped_attributes[0]['value']); @@ -1204,7 +1258,7 @@ public function test_get_unmapped_attributes_mixed() { */ public function test_get_unmapped_attributes_empty_values() { $product = WC_Helper_Product::create_simple_product(); - + // Add attributes with empty values $attributes = array(); $attributes[] = $this->create_product_attribute('weight', '', false); // empty unmapped @@ -1215,10 +1269,10 @@ public function test_get_unmapped_attributes_empty_values() { $facebook_product = new \WC_Facebook_Product($product); $unmapped_attributes = $facebook_product->get_unmapped_attributes(); - + $this->assertIsArray($unmapped_attributes); $this->assertCount(1, $unmapped_attributes); - + // Verify only non-empty unmapped attribute is returned $this->assertEquals('style', $unmapped_attributes[0]['name']); $this->assertEquals('Modern', $unmapped_attributes[0]['value']);