if ( ! isset( $this->requests[ $index ] ) || self::ACTION_DELETE !== $this->requests[ $index ] ) { $this->requests[ $index ] = self::ACTION_UPDATE; } } } /** * Adds the given retailer IDs to the requests array to be deleted. * * @since 2.0.0 * * @param int[] $retailer_ids retailer IDs to delete */ public function delete_products( array $retailer_ids ) { foreach ( $retailer_ids as $retailer_id ) { $this->requests[ $this->get_product_index( $retailer_id ) ] = self::ACTION_DELETE; } } /** * Adds the products with stock changes to the requests array to be updated. * * @since 3.5.8 * * @param \WC_Product $product product object */ public function handle_stock_update( \WC_Product $product ) { // bail if not connected if ( ! facebook_for_woocommerce()->get_connection_handler()->is_connected() ) { return; } // bail if admin and not AJAX if ( is_admin() && ! wp_doing_ajax() ) { return; } try { // handle variable products - sync children only, not the parent if ( $product->is_type( 'variable' ) ) { $variation_ids = $product->get_children(); if ( ! empty( $variation_ids ) ) { $this->create_or_update_products( $variation_ids ); } } else { // add the product to the list of products to be updated $this->create_or_update_products( array( $product->get_id() ) ); } } catch ( Exception $e ) { // Silently handle any sync errors - Facebook sync should not break stock updates. unset( $e ); // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch } } /** * Handles product imports from WooCommerce import functionality. * * @since 3.5.8 * * @param \WC_Product $product The product object that was imported * @param array $data The import data */ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter public function handle_product_import_update( $product, $data ) { // bail if not connected if ( ! facebook_for_woocommerce()->get_connection_handler()->is_connected() ) { return; } // Process ALL products (both new and updates) during import try { facebook_for_woocommerce()->get_product_sync_validator( $product )->validate(); $this->create_or_update_products( array( $product->get_id() ) ); } catch ( \Exception $e ) { return; } } /** * Creates a background job to sync the products in the requests array. * * @since 2.0.0 * * @return \stdClass|object|null */ public function schedule_sync() { if ( ! empty( $this->requests ) ) { $job_handler = facebook_for_woocommerce()->get_products_sync_background_handler(); $job = $job_handler->create_job( array( 'requests' => $this->requests ) ); $job_handler->dispatch(); return $job; } } /** * Gets the prefixed product ID used as the array index. * * @since 2.0.0 * * @param int|string $product_id product ID * @return string prefixed product index */ private function get_product_index( $product_id ) { return self::PRODUCT_INDEX_PREFIX . $product_id; } /** * Determines whether a sync is currently in progress. * * This method uses caching to avoid expensive database queries. * The cache is automatically invalidated when jobs are created, updated, or completed. * On frontend requests, this always returns false to avoid expensive queries. * * @since 2.0.0 * * @return bool */ public static function is_sync_in_progress() { // On frontend, skip the check entirely - visitors don't need sync status if ( ! is_admin() && ! wp_doing_ajax() && ! wp_doing_cron() ) { return false; } // Check cache first $cache_key = 'wc_facebook_sync_in_progress'; $cached = get_transient( $cache_key ); if ( false !== $cached ) { return 'yes' === $cached; } // Query for processing jobs $jobs = facebook_for_woocommerce()->get_products_sync_background_handler()->get_jobs( array( 'status' => 'processing', ) ); $in_progress = ! empty( $jobs ); // Cache the result - invalidated when job status changes set_transient( $cache_key, $in_progress ? 'yes' : 'no', 0 ); return $in_progress; } }