diff --git a/README.md b/README.md index 653a0153f..354639df2 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ Grow your business on Facebook and Instagram. Easily promote your products and t - Sell from one inventory that automatically syncs to your catalog used for ads +### This is the development repository for the Facebook for WooCommerce plugin. +**We're excited to announce that the app is now owned by Meta, and we invite the developer community to join us in shaping its future through contributions.** + +Grow your business on Facebook and Instagram. Easily promote your products and target accurately using powerful sales and marketing tools. Reach new customers and drive traffic to your website with seamless ad experiences, from discovery to conversion. Automatically sync your eligible products to your Meta catalog, so you can easily create ads right where your customers are. +- Help drive better ad performance by setting up a conversion pixel +- Easily set up your ads with a one-time account connection +- Sell from one inventory that automatically syncs to your catalog used for ads + + ### This is the development repository for the Facebook for WooCommerce plugin. - [WooCommerce.com product page](https://woocommerce.com/products/facebook/) diff --git a/changelog.txt b/changelog.txt index 12543f60d..a1d82530c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,19 @@ *** Facebook for WooCommerce Changelog *** += 3.4.4 - 2025-03-26 = +* Add - Create tests for ProductFeedUploads create endpoint by @ajello-meta in #2902 +* Add - Create tests for ProductFeedUploads read endpoint by @ajello-meta in #2903 +* Tweak - Remove phpcs:ignoreFile annotation + Enable code coverage report generation with phpunit by @sol-loup in #2897 and #2901 +* Fix - Restores the original dynamic property behavior in the AsyncRequest class by @sol-loup in #2921 +* Tweak - Changing APP to PLUGIN on README.MD by @SayanPandey in #2916 +* Tweak - Update README.md - Added noification for ownership transfer by @SayanPandey in #2910 +* Tweak - Added is_multisite logging to the update_plugin_version_configuration request by @carterbuce in #2955 +* Tweak - Add woo_commerce_retailer_id to products API request by @crisojog in #2958 +* Tweak - Syncing plugin version info by @vinkmeta in #2960 +* Fix - sync products out of stock to meta despite visibility config by @francorisso in #2952 +* Fix - Update woo_commerce_retailer_id to existing field external_variant_id by @crisojog in #2963 +* Tweak - Update readme.txt by @vinkmeta in #2949 + = 3.4.3 - 2025-03-19 = * Add - Items batch request and response tests by @nrostrow-meta in #2917 * Tweak - Always run PHP-based github workflows by @carterbuce in #2926 diff --git a/facebook-config-warmer.php b/facebook-config-warmer.php index 5882ca298..8e9cd3f50 100644 --- a/facebook-config-warmer.php +++ b/facebook-config-warmer.php @@ -1,5 +1,4 @@ set_plugin_version( $plugin_version ); + $request->set_external_client_metadata( + array( + 'version_id' => $plugin_version, + 'is_multisite' => is_multisite(), + ) + ); $this->set_response_handler( API\FBE\Configuration\Update\Response::class ); return $this->perform_request( $request ); } diff --git a/includes/API/FBE/Configuration/Update/Request.php b/includes/API/FBE/Configuration/Update/Request.php index 36cea944a..498bd3277 100644 --- a/includes/API/FBE/Configuration/Update/Request.php +++ b/includes/API/FBE/Configuration/Update/Request.php @@ -35,21 +35,20 @@ public function __construct( $external_business_id ) { $this->data['fbe_external_business_id'] = $external_business_id; } - /** - * Sets the plugin version for configuration update request. + * Sets the external client metadata for logging + * + * @since 3.4.4 * - * @since 3.0.10 + * @param array $metadata map of metadata to include. Example: array ('version_id' => '0.0.0', 'is_multisite' => True) * - * @param string $plugin_version current plugin version. + * @return void */ - public function set_plugin_version( string $plugin_version ) { - + public function set_external_client_metadata( array $metadata ) { $this->data['business_config'] = array( - 'external_client' => - array( - 'version_id' => "$plugin_version", - ), + 'external_client' => $metadata, ); + + is_multisite(); } } diff --git a/includes/Admin/Settings_Screens/Product_Sets.php b/includes/Admin/Settings_Screens/Product_Sets.php index 932c79bc4..f110c036e 100644 --- a/includes/Admin/Settings_Screens/Product_Sets.php +++ b/includes/Admin/Settings_Screens/Product_Sets.php @@ -1,5 +1,4 @@ log( 'debug', $message, array( 'source' => 'facebook_for_woocommerce_profiling' ) ); } - } diff --git a/includes/Debug/ProfilingLoggerProcess.php b/includes/Debug/ProfilingLoggerProcess.php index d49938dec..037089767 100644 --- a/includes/Debug/ProfilingLoggerProcess.php +++ b/includes/Debug/ProfilingLoggerProcess.php @@ -1,5 +1,4 @@ stop_time - $this->start_time; } - } diff --git a/includes/Events/AAMSettings.php b/includes/Events/AAMSettings.php index 86a9b8264..58cee4346 100644 --- a/includes/Events/AAMSettings.php +++ b/includes/Events/AAMSettings.php @@ -1,5 +1,4 @@ get_connection_handler()->is_connected() ) { diff --git a/includes/Feed/FeedConfigurationDetection.php b/includes/Feed/FeedConfigurationDetection.php index 1c6e57d4c..8184275a1 100644 --- a/includes/Feed/FeedConfigurationDetection.php +++ b/includes/Feed/FeedConfigurationDetection.php @@ -1,5 +1,4 @@ get_api()->read_feeds($product_catalog_id); + $response = facebook_for_woocommerce()->get_api()->read_feeds( $product_catalog_id ); } catch ( \Exception $e ) { $message = sprintf( 'There was an error trying to get feed nodes for catalog: %s', $e->getMessage() ); facebook_for_woocommerce()->log( $message ); @@ -213,8 +205,8 @@ private function get_feed_nodes_for_catalog( string $product_catalog_id ) { * @param string $feed_id Facebook Product Feed ID. * * @return Response - * @throws Request_Limit_Reached - * @throws ApiException + * @throws Request_Limit_Reached When API request limit is reached. + * @throws ApiException When there is an error in the API request. */ private function get_feed_metadata( string $feed_id ) { return facebook_for_woocommerce()->get_api()->read_feed( $feed_id ); @@ -230,7 +222,7 @@ private function get_feed_metadata( string $feed_id ) { */ private function get_feed_upload_metadata( $upload_id ) { try { - $response = facebook_for_woocommerce()->get_api()->read_upload($upload_id); + $response = facebook_for_woocommerce()->get_api()->read_upload( $upload_id ); } catch ( \Exception $e ) { $message = sprintf( 'There was an error trying to get feed upload metadata: %s', $e->getMessage() ); facebook_for_woocommerce()->log( $message ); @@ -238,5 +230,4 @@ private function get_feed_upload_metadata( $upload_id ) { } return $response; } - } diff --git a/includes/Framework/Plugin/Exception.php b/includes/Framework/Plugin/Exception.php index f0601ba28..f581034e9 100644 --- a/includes/Framework/Plugin/Exception.php +++ b/includes/Framework/Plugin/Exception.php @@ -1,12 +1,11 @@ identifier = $this->prefix . '_' . $this->action; - add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); + add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); } @@ -87,7 +85,10 @@ public function dispatch() { */ protected function get_query_args() { + // Check if a child class has defined this property for custom query args if ( property_exists( $this, 'query_args' ) ) { + // Dynamic property; not defined in the parent class, and not a true lint error + // phpcs:ignore return $this->query_args; } @@ -106,7 +107,10 @@ protected function get_query_args() { */ protected function get_query_url() { + // Check if a child class has defined this property for a custom URL if ( property_exists( $this, 'query_url' ) ) { + // Dynamic property; not defined in the parent class, and not a true lint error + // phpcs:ignore return $this->query_url; } @@ -124,7 +128,10 @@ protected function get_query_url() { */ protected function get_request_args() { + // Check if a child class has defined this property for custom request args if ( property_exists( $this, 'request_args' ) ) { + // Dynamic property; not defined in the parent class, and not a true lint error + // phpcs:ignore return $this->request_args; } @@ -142,6 +149,7 @@ protected function get_request_args() { * Maybe handle * * Check for correct nonce and pass to handler. + * * @since 4.4.0 */ public function maybe_handle() { diff --git a/includes/Handlers/WebHook.php b/includes/Handlers/WebHook.php index 04f09c2a0..a9db78bbc 100644 --- a/includes/Handlers/WebHook.php +++ b/includes/Handlers/WebHook.php @@ -1,5 +1,4 @@ stop( $process_name ); } - } diff --git a/includes/Jobs/GenerateProductFeed.php b/includes/Jobs/GenerateProductFeed.php index 28c4e2bc7..7386a2adf 100644 --- a/includes/Jobs/GenerateProductFeed.php +++ b/includes/Jobs/GenerateProductFeed.php @@ -1,6 +1,4 @@ rename_temporary_feed_file_to_final_feed_file(); facebook_for_woocommerce()->get_tracker()->save_batch_generation_time(); - do_action('wc_facebook_feed_generation_completed'); + do_action( 'wc_facebook_feed_generation_completed' ); } /** @@ -72,7 +71,7 @@ protected function get_items_for_batch( int $batch_number, array $args ): array return array_map( 'intval', $product_ids ); } -/** + /** * Processes a batch of items. * * @since 1.1.0 @@ -85,6 +84,7 @@ protected function get_items_for_batch( int $batch_number, array $args ): array protected function process_items( array $items, array $args ) { // Grab start time. $start_time = microtime( true ); + /* * Pre-fetch full product objects. * Variable products will be filtered out here since we don't need them for the feed. It's important to not @@ -111,6 +111,9 @@ protected function process_items( array $items, array $args ) { /** * Empty function to satisfy parent class requirements. * We don't use it because we are processing the whole batch at once in process_items. + * + * @param mixed $item The item to process. + * @param array $args The args for the job. */ protected function process_item( $item, array $args ) {} @@ -140,5 +143,4 @@ public function get_plugin_name(): string { protected function get_batch_size(): int { return 15; } - } diff --git a/includes/Jobs/JobManager.php b/includes/Jobs/JobManager.php index f04d6fd9c..a15bba36e 100644 --- a/includes/Jobs/JobManager.php +++ b/includes/Jobs/JobManager.php @@ -1,5 +1,4 @@ cleanup_skyverge_job_options = new CleanupSkyvergeFrameworkJobOptions(); $this->cleanup_skyverge_job_options->init(); - $this->reset_all_product_fb_settings = new ResetAllProductsFBSettings( $action_scheduler_proxy); + $this->reset_all_product_fb_settings = new ResetAllProductsFBSettings( $action_scheduler_proxy ); $this->reset_all_product_fb_settings->init(); - $this->delete_all_products = new DeleteProductsFromFBCatalog( $action_scheduler_proxy); + $this->delete_all_products = new DeleteProductsFromFBCatalog( $action_scheduler_proxy ); $this->delete_all_products->init(); } - } diff --git a/includes/Jobs/LoggingTrait.php b/includes/Jobs/LoggingTrait.php index 4cbf15a4d..e87526b49 100644 --- a/includes/Jobs/LoggingTrait.php +++ b/includes/Jobs/LoggingTrait.php @@ -1,5 +1,4 @@ categories_field, true ); + self::$prev_product_cat = get_term_meta( $term_id, $this->categories_field, true ); self::$prev_product_name = get_term( $term_id )->name; } @@ -268,7 +268,7 @@ public function fb_product_set_hook_before( $term_id ) { * @param int $term_id Term ID. */ public function fb_product_set_hook_after( $term_id ) { - self::$new_product_cat = get_term_meta( $term_id, $this->categories_field, true ); + self::$new_product_cat = get_term_meta( $term_id, $this->categories_field, true ); self::$new_product_name = get_term( $term_id )->name; if ( ! empty( $this->get_all_diff( 'product_cat' ) ) || self::$prev_product_name !== self::$new_product_name ) { $this->maybe_sync_product_set( $term_id ); @@ -464,6 +464,4 @@ protected function get_all_diff( $taxonomy = 'product_cat' ) { return array_merge( $removed, $added ); } - - } diff --git a/includes/ProductSync/ProductValidator.php b/includes/ProductSync/ProductValidator.php index e839ef6ca..f9e397e82 100644 --- a/includes/ProductSync/ProductValidator.php +++ b/includes/ProductSync/ProductValidator.php @@ -133,7 +133,6 @@ public function __get( $key ) { public function validate() { $this->validate_sync_enabled_globally(); $this->validate_product_status(); - $this->validate_product_stock_status(); $this->validate_product_sync_field(); $this->validate_product_price(); $this->validate_product_visibility(); @@ -151,7 +150,6 @@ public function validate() { */ public function validate_but_skip_status_check() { $this->validate_sync_enabled_globally(); - $this->validate_product_stock_status(); $this->validate_product_sync_field(); $this->validate_product_price(); $this->validate_product_visibility(); @@ -168,7 +166,6 @@ public function validate_but_skip_status_check() { */ public function validate_but_skip_sync_field() { $this->validate_sync_enabled_globally(); - $this->validate_product_stock_status(); $this->validate_product_price(); $this->validate_product_visibility(); $this->validate_product_terms(); @@ -268,17 +265,6 @@ protected function validate_product_status() { } } - /** - * Check whether the product should be excluded due to being out of stock. - * - * @throws ProductExcludedException If product should not be synced. - */ - protected function validate_product_stock_status() { - if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->product->is_in_stock() ) { - throw new ProductExcludedException( __( 'Product must be in stock.', 'facebook-for-woocommerce' ) ); - } - } - /** * Check whether the product's visibility excludes it from sync. * diff --git a/includes/Product_Categories.php b/includes/Product_Categories.php index 1b164e33f..caadcfd01 100644 --- a/includes/Product_Categories.php +++ b/includes/Product_Categories.php @@ -1,5 +1,4 @@ is_in_stock() ) || ! facebook_for_woocommerce()->get_product_sync_validator( $product )->passes_product_terms_check(); + return ! facebook_for_woocommerce()->get_product_sync_validator( $product )->passes_product_terms_check(); } diff --git a/includes/Products/FBCategories.php b/includes/Products/FBCategories.php index 91270ccdd..0d1655923 100644 --- a/includes/Products/FBCategories.php +++ b/includes/Products/FBCategories.php @@ -1,5 +1,4 @@ true]; + /** @var array Keys to exclude from attribute processing */ + private $keys_to_exclude = [ 'brand' => true ]; /** * Fetches the attribute from a category using attribute key. diff --git a/includes/Products/Feed.php b/includes/Products/Feed.php index ccf3fb830..75114dd79 100644 --- a/includes/Products/Feed.php +++ b/includes/Products/Feed.php @@ -1,5 +1,4 @@ Feed::get_feed_data_url(), + 'url' => self::get_feed_data_url(), ]; try { @@ -212,8 +212,8 @@ public function send_request_to_upload_feed() { /** * Retrieves or creates an integration feed ID - * - * @return string the integration feed ID + * + * @return string the integration feed ID * * @internal */ @@ -221,27 +221,27 @@ public function retrieve_or_create_integration_feed_id() { // Step 1 - Get feed ID if it is already available in local cache $feed_id = facebook_for_woocommerce()->get_integration()->get_feed_id(); if ( $feed_id ) { - if ( self::validate_feed_exists($feed_id) ) { - WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', from local cache was validated.'); + if ( self::validate_feed_exists( $feed_id ) ) { + WC_Facebookcommerce_Utils::log( 'Feed: feed_id = ' . $feed_id . ', from local cache was validated.' ); return $feed_id; } else { - WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', from local cache was invalidated.'); + WC_Facebookcommerce_Utils::log( 'Feed: feed_id = ' . $feed_id . ', from local cache was invalidated.' ); } } // Step 2 - Query feeds data from Meta and filter the right one $feed_id = self::query_and_filter_integration_feed_id(); if ( $feed_id ) { - facebook_for_woocommerce()->get_integration()->update_feed_id($feed_id); - WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', queried and filtered from Meta API.'); + facebook_for_woocommerce()->get_integration()->update_feed_id( $feed_id ); + WC_Facebookcommerce_Utils::log( 'Feed: feed_id = ' . $feed_id . ', queried and filtered from Meta API.' ); return $feed_id; } // Step 3 - Create a new feed $feed_id = self::create_feed_id(); if ( $feed_id ) { - facebook_for_woocommerce()->get_integration()->update_feed_id($feed_id); - WC_Facebookcommerce_Utils::log( 'Feed: feed_id = '.$feed_id.', created a new feed via Meta API.'); + facebook_for_woocommerce()->get_integration()->update_feed_id( $feed_id ); + WC_Facebookcommerce_Utils::log( 'Feed: feed_id = ' . $feed_id . ', created a new feed via Meta API.' ); return $feed_id; } @@ -251,13 +251,14 @@ public function retrieve_or_create_integration_feed_id() { /** * Validates that provided feed ID still exists on the Meta side * - * @param string $feed_id the feed ID - * - * @return bool true if the feed ID is valid - * + * @param string $feed_id the feed ID + * + * @return bool true if the feed ID is valid + * * @internal + * @throws Exception|Error If there is an error getting feed nodes or if no catalog ID is available. */ - private function validate_feed_exists($feed_id) { + private function validate_feed_exists( $feed_id ) { try { $catalog_id = facebook_for_woocommerce()->get_integration()->get_product_catalog_id(); if ( '' === $catalog_id ) { @@ -271,7 +272,7 @@ private function validate_feed_exists($feed_id) { } foreach ( $feed_nodes as $feed ) { - if ($feed['id'] == $feed_id) { + if ( $feed['id'] == $feed_id ) { return true; } } @@ -282,10 +283,11 @@ private function validate_feed_exists($feed_id) { /** * Queries existing feeds for the integration catalog and filters * the plugin integration feed ID - * - * @return string the integration feed ID - * + * + * @return string the integration feed ID + * * @internal + * @throws Exception|Error If there is an error getting feed nodes, catalog, or if no catalog ID is available. */ private function query_and_filter_integration_feed_id() { try { @@ -303,7 +305,7 @@ private function query_and_filter_integration_feed_id() { if ( empty( $feed_nodes ) ) { return ''; } - + try { $catalog = facebook_for_woocommerce()->get_api()->get_catalog( $catalog_id ); } catch ( Exception $e ) { @@ -346,10 +348,11 @@ private function query_and_filter_integration_feed_id() { /** * Makes a request to Meta to create a new feed - * - * @return string the integration feed ID - * + * + * @return string the integration feed ID + * * @internal + * @throws Exception|Error If there is an error creating the feed or if no catalog ID is available. */ private function create_feed_id() { try { @@ -385,7 +388,7 @@ private function is_fpassthru_disabled() { $disabled = false; if ( function_exists( 'ini_get' ) ) { $disabled_functions = @ini_get( 'disable_functions' ); - $disabled = is_string( $disabled_functions ) && in_array( 'fpassthru', explode( ',', $disabled_functions ), false ); + $disabled = is_string( $disabled_functions ) && in_array( 'fpassthru', explode( ',', $disabled_functions ), false ); } return $disabled; } diff --git a/includes/Products/GoogleProductTaxonomy.php b/includes/Products/GoogleProductTaxonomy.php index 30d045c14..ad384de1b 100644 --- a/includes/Products/GoogleProductTaxonomy.php +++ b/includes/Products/GoogleProductTaxonomy.php @@ -1,5 +1,4 @@ queue->add( self::DAILY ); } - - } diff --git a/includes/fbasync.php b/includes/fbasync.php index 9e229517c..bd79a088f 100644 --- a/includes/fbasync.php +++ b/includes/fbasync.php @@ -1,5 +1,4 @@ commerce = $commerce; // Full WC_Facebookcommerce_Integration obj - } + public function __construct( $commerce ) { + $this->commerce = $commerce; // Full WC_Facebookcommerce_Integration obj + } /** * __get method for backward compatibility. @@ -34,41 +38,41 @@ public function __construct( $commerce ) { * @return mixed * @since 3.0.32 */ - public function __get( $key ) { - // Add warning for private properties. - if ( 'commerce' === $key ) { - /* 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; - } - - return null; + public function __get( $key ) { + // Add warning for private properties. + if ( 'commerce' === $key ) { + /* 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; } + return null; + } + /** * @var string */ protected $action = 'fb_commerce_background_process'; - public function dispatch() { - $commerce = $this->commerce; - $dispatched = parent::dispatch(); - - if ( is_wp_error( $dispatched ) ) { - WC_Facebookcommerce_Utils::log( - sprintf( - 'Unable to dispatch FB Background processor: %s', - $dispatched->get_error_message() - ), - array( 'source' => 'wc_facebook_background_process' ) - ); - } + public function dispatch() { + $commerce = $this->commerce; + $dispatched = parent::dispatch(); + + if ( is_wp_error( $dispatched ) ) { + WC_Facebookcommerce_Utils::log( + sprintf( + 'Unable to dispatch FB Background processor: %s', + $dispatched->get_error_message() + ), + array( 'source' => 'wc_facebook_background_process' ) + ); } + } - public function get_item_count() { - $commerce = $this->commerce; - return (int) get_transient( $commerce::FB_SYNC_REMAINING ); - } + public function get_item_count() { + $commerce = $this->commerce; + return (int) get_transient( $commerce::FB_SYNC_REMAINING ); + } /** * Handle cron healthcheck @@ -76,55 +80,54 @@ public function get_item_count() { * Restart the background process if not already running * and data exists in the queue. */ - public function handle_cron_healthcheck() { - $commerce = $this->commerce; - if ( $this->is_process_running() ) { - // Background process already running, no-op - return true; // Return "is running? status" - } - - if ( $this->is_queue_empty() ) { - // No data to process. - $this->clear_scheduled_event(); - delete_transient( $commerce::FB_SYNC_REMAINING ); - return; - } - - $this->handle(); - return true; + public function handle_cron_healthcheck() { + $commerce = $this->commerce; + if ( $this->is_process_running() ) { + // Background process already running, no-op + return true; // Return "is running? status" + } + if ( $this->is_queue_empty() ) { + // No data to process. + $this->clear_scheduled_event(); + delete_transient( $commerce::FB_SYNC_REMAINING ); + return; } + $this->handle(); + return true; + } + /** * Schedule fallback event. */ - protected function schedule_event() { - if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { - wp_schedule_event( - time() + 10, - $this->cron_interval_identifier, - $this->cron_hook_identifier - ); - } + protected function schedule_event() { + if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { + wp_schedule_event( + time() + 10, + $this->cron_interval_identifier, + $this->cron_hook_identifier + ); } + } /** * Is the processor updating? * * @return boolean */ - public function is_updating() { - return false === $this->is_queue_empty(); - } + public function is_updating() { + return false === $this->is_queue_empty(); + } /** * Is the processor running? * * @return boolean */ - public function is_running() { - return $this->is_process_running(); - } + public function is_running() { + return $this->is_process_running(); + } /** * Process individual product @@ -136,30 +139,30 @@ public function is_running() { * * @return mixed */ - protected function task( $item ) { - $commerce = $this->commerce; // PHP5 compatibility for static access - $remaining = $this->get_item_count(); - $count_message = sprintf( - 'Background syncing products to Facebook. Products remaining: %1$d', - $remaining - ); - - $this->commerce->display_sticky_message( $count_message, true ); - - $this->commerce->on_product_publish( $item ); - $remaining--; - set_transient( - $commerce::FB_SYNC_IN_PROGRESS, - true, - $commerce::FB_SYNC_TIMEOUT - ); - set_transient( - $commerce::FB_SYNC_REMAINING, - $remaining - ); - - return false; - } + protected function task( $item ) { + $commerce = $this->commerce; // PHP5 compatibility for static access + $remaining = $this->get_item_count(); + $count_message = sprintf( + 'Background syncing products to Facebook. Products remaining: %1$d', + $remaining + ); + + $this->commerce->display_sticky_message( $count_message, true ); + + $this->commerce->on_product_publish( $item ); + --$remaining; + set_transient( + $commerce::FB_SYNC_IN_PROGRESS, + true, + $commerce::FB_SYNC_TIMEOUT + ); + set_transient( + $commerce::FB_SYNC_REMAINING, + $remaining + ); + + return false; + } /** * Complete @@ -167,15 +170,14 @@ protected function task( $item ) { * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ - protected function complete() { - $commerce = $this->commerce; // PHP5 compatibility for static access - delete_transient( $commerce::FB_SYNC_IN_PROGRESS ); - delete_transient( $commerce::FB_SYNC_REMAINING ); - WC_Facebookcommerce_Utils::log( 'Background sync complete!' ); - WC_Facebookcommerce_Utils::fblog( 'Background sync complete!' ); - $this->commerce->remove_sticky_message(); - $this->commerce->display_info_message( 'Facebook product sync complete!' ); - parent::complete(); - } - + protected function complete() { + $commerce = $this->commerce; // PHP5 compatibility for static access + delete_transient( $commerce::FB_SYNC_IN_PROGRESS ); + delete_transient( $commerce::FB_SYNC_REMAINING ); + WC_Facebookcommerce_Utils::log( 'Background sync complete!' ); + WC_Facebookcommerce_Utils::fblog( 'Background sync complete!' ); + $this->commerce->remove_sticky_message(); + $this->commerce->display_info_message( 'Facebook product sync complete!' ); + parent::complete(); } +} diff --git a/includes/fbproduct.php b/includes/fbproduct.php index 6d65e05b4..12a3b41b5 100644 --- a/includes/fbproduct.php +++ b/includes/fbproduct.php @@ -832,6 +832,7 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel $product_data[ 'availability' ] = $this->is_in_stock() ? 'in stock' : 'out of stock'; $product_data[ 'visibility' ] = Products::is_product_visible( $this->woo_product ) ? \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE : \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN; $product_data[ 'retailer_id' ] = $retailer_id; + $product_data[ 'external_variant_id' ] = $this->get_id(); if ( self::PRODUCT_PREP_TYPE_ITEMS_BATCH === $type_to_prepare_for ) { $product_data['title'] = WC_Facebookcommerce_Utils::clean_string( $this->get_title() ); diff --git a/includes/test/fbproductfeed-test.php b/includes/test/fbproductfeed-test.php index 3e5fed167..2af1810c8 100644 --- a/includes/test/fbproductfeed-test.php +++ b/includes/test/fbproductfeed-test.php @@ -1,5 +1,4 @@ assertEquals('product_feed_upload_id', $response->id); + $this->assertEquals('success', $response->data['upload_status']); + } +} diff --git a/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Create/RequestTest.php b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Create/RequestTest.php new file mode 100644 index 000000000..4e7f4f532 --- /dev/null +++ b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Create/RequestTest.php @@ -0,0 +1,30 @@ + 'Test Product Feed', + 'schedule' => [ + 'interval' => 'DAILY', + 'url' => 'http://example.com/feed.xml', + ], + ]; + + $request = new Request($product_feed_id, $data); + + $this->assertEquals('POST', $request->get_method()); + $this->assertEquals('/product_feed_upload_id/uploads', $request->get_path()); + $this->assertEquals($data, $request->get_data()); + } +} diff --git a/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/RequestTest.php b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/RequestTest.php new file mode 100644 index 000000000..65e719ac7 --- /dev/null +++ b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/RequestTest.php @@ -0,0 +1,23 @@ +assertEquals('GET', $request->get_method()); + $this->assertEquals($expected_path, $request->get_path()); + } +} diff --git a/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/ResponseTest.php b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/ResponseTest.php new file mode 100644 index 000000000..c92959b61 --- /dev/null +++ b/tests/Unit/Api/ProductCatalog/ProductFeedUploads/Read/ResponseTest.php @@ -0,0 +1,37 @@ +assertEquals('product_feed_upload_id', $response->id); + $this->assertEquals(0, $response->data['error_count']); + $this->assertEquals(2, $response->data['warning_count']); + $this->assertEquals(100, $response->data['num_detected_items']); + $this->assertEquals(98, $response->data['num_persisted_items']); + $this->assertEquals('http://example.com/feed.xml', $response->data['url']); + $this->assertEquals('2023-10-01T12:00:00+0000', $response->data['end_time']); + } +} diff --git a/tests/Unit/ExternalVersionUpdate/UpdateTest.php b/tests/Unit/ExternalVersionUpdate/UpdateTest.php index cbbea2d2d..67a90c037 100644 --- a/tests/Unit/ExternalVersionUpdate/UpdateTest.php +++ b/tests/Unit/ExternalVersionUpdate/UpdateTest.php @@ -40,12 +40,6 @@ public function setUp():void { */ public function test_should_update_version() { $plugin = facebook_for_woocommerce(); - - // Assert update not required when the versions match. - update_option( 'facebook_for_woocommerce_latest_version_sent_to_server', WC_Facebookcommerce_Utils::PLUGIN_VERSION ); - $should_update = $this->update->should_update_version(); - $this->assertFalse( $should_update ); - /** * Set the $plugin->connection_handler and $plugin->api access to true. This will allow us * to assign the mock objects to these properties. @@ -61,7 +55,6 @@ public function test_should_update_version() { ->getMock(); $mock_connection_handler->expects( $this->any() )->method( 'is_connected' )->willReturn( false ); $prop_connection_handler->setValue( $plugin, $mock_connection_handler ); - update_option( 'facebook_for_woocommerce_latest_version_sent_to_server', '0.0.0' ); // Reset the option. $should_update2 = $this->update->should_update_version(); $this->assertFalse( $should_update2 ); @@ -75,7 +68,7 @@ public function test_should_update_version() { $prop_connection_handler->setValue( $plugin, $mock_connection_handler ); update_option( 'facebook_for_woocommerce_latest_version_sent_to_server', WC_Facebookcommerce_Utils::PLUGIN_VERSION ); $should_update3 = $this->update->should_update_version(); - $this->assertFalse( $should_update3 ); // Because the versions match. + $this->assertTrue( $should_update3 ); // Because the versions match. update_option( 'facebook_for_woocommerce_latest_version_sent_to_server', '0.0.0' ); // Reset the option. $should_update4 = $this->update->should_update_version(); @@ -128,6 +121,7 @@ public function test_maybe_update_external_plugin_version() { 'business_config' => array( 'external_client' => array( 'version_id' => WC_Facebookcommerce_Utils::PLUGIN_VERSION, + 'is_multisite' => false, ), ), ); diff --git a/tests/Unit/WCFacebookCommerceIntegrationTest.php b/tests/Unit/WCFacebookCommerceIntegrationTest.php index 40d0198fc..aecf59db2 100644 --- a/tests/Unit/WCFacebookCommerceIntegrationTest.php +++ b/tests/Unit/WCFacebookCommerceIntegrationTest.php @@ -909,13 +909,13 @@ public function test_delete_on_out_of_stock_deletes_simple_product() { add_post_meta( $product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, 'facebook-product-item-id' ); - $this->api->expects( $this->once() ) + $this->api->expects( $this->never() ) ->method( 'delete_product_item' ) ->with( 'facebook-product-item-id' ); $result = $this->integration->delete_on_out_of_stock( $product->get_id(), $product ); - $this->assertTrue( $result ); + $this->assertFalse( $result ); } /**