HEX
Server: Apache/2.4.37 (CentOS Stream) OpenSSL/1.1.1k
System: Linux ysnet.com.tw 4.18.0-553.5.1.el8.x86_64 #1 SMP Tue May 21 05:46:01 UTC 2024 x86_64
User: test (521)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: /var/www/test/wp-content/plugins/pinterest-for-woocommerce/src/FeedStatusService.php
<?php
/**
 * Service class to handle & return Pinterest Feed Status
 *
 * @package     Automattic\WooCommerce\Pinterest
 * @version     1.3.0
 */

namespace Automattic\WooCommerce\Pinterest;

use Automattic\WooCommerce\Pinterest\API\Base;
use Exception;
use Throwable;

defined( 'ABSPATH' ) || exit;

/**
 * Class handling methods to return Pinterest Feed Status.
 */
class FeedStatusService {

	/**
	 * Plugin data key used to avoid logging the same failed processing result for the same feed repeatedly.
	 *
	 * @var string
	 */
	private const LAST_LOGGED_PROCESSING_RESULT_ID_KEY = 'last_logged_processing_result_id';

	/**
	 * Plugin data key used to avoid retrying the same failed processing result repeatedly.
	 *
	 * @var string
	 */
	private const LAST_RETRIED_PROCESSING_RESULT_ID_KEY = 'last_retried_processing_result_id';

	/**
	 * The error contexts to search for in the workflow responses.
	 *
	 * @var array
	 */
	const ERROR_CONTEXTS = array(
		'validation_stats_warnings',
		'ingestion_stats_warnings',
		'validation_stats_errors',
		'ingestion_stats_errors',
	);

	/**
	 * The error codes that are related to the feed itself, and not a specific product.
	 * Source: https://help.pinterest.com/en/business/article/data-source-ingestion
	 *
	 * @var array
	 */
	private const GLOBAL_ERROR_CODES = array(
		// Validation errors.
		'FETCH_INACTIVE_FEED_ERROR', // => 3,
		'FETCH_ERROR',               // => 100,
		'ENCODING_ERROR',            // => 101,
		'DELIMITER_ERROR',           // => 102,
		'REQUIRED_COLUMNS_MISSING',  // => 103,
		'INTERNAL_SERVICE_ERROR',    // => 128,
		'NO_VERIFIED_DOMAIN',        // => 140,
		'MALFORMED_XML',             // => 143,
		'FEED_TOO_SMALL',            // => 152,

		// Ingestion errors.
		'LARGE_PRODUCT_COUNT_DECREASE',
		'ACCOUNT_FLAGGED',
	);

	public const ERROR_MESSAGES = array(
		// Validation errors.
		'FETCH_ERROR'                            => 'Pinterest couldn\'t download your feed.',
		'FETCH_INACTIVE_FEED_ERROR'              => 'Your feed wasn\'t ingested because it hasn\'t changed in the previous 90 days.',
		'ENCODING_ERROR'                         => 'Your feed includes data with an unsupported encoding format.',
		'DELIMITER_ERROR'                        => 'Your feed includes data with formatting errors.',
		'REQUIRED_COLUMNS_MISSING'               => 'Your feed is missing some required column headers.',
		'DUPLICATE_PRODUCTS'                     => 'Some products are duplicated.',
		'IMAGE_LINK_INVALID'                     => 'Some image links are formatted incorrectly.',
		'ITEMID_MISSING'                         => 'Some items are missing an item id in their product metadata, those items will not be published.',
		'TITLE_MISSING'                          => 'Some items are missing a title in their product metadata, those items will not be published.',
		'DESCRIPTION_MISSING'                    => 'Some items are missing a description in their product metadata, those items will not be published.',
		'PRODUCT_LINK_MISSING'                   => 'Some items are missing a link URL in their product metadata, those items will not be published.',
		'IMAGE_LINK_MISSING'                     => 'Some items are missing an image link URL in their product metadata, those items will not be published.',
		'AVAILABILITY_INVALID'                   => 'Some items are missing an availability value in their product metadata, those items will not be published.',
		'PRODUCT_PRICE_INVALID'                  => 'Some items have price formatting errors in their product metadata, those items will not be published.',
		'LINK_FORMAT_INVALID'                    => 'Some link values are formatted incorrectly.',
		'PARSE_LINE_ERROR'                       => 'Your feed contains formatting errors for some items.',
		'ADWORDS_FORMAT_INVALID'                 => 'Some adwords links contain too many characters.',
		'INTERNAL_SERVICE_ERROR'                 => 'We experienced a technical difficulty and were unable to ingest your feed. The next ingestion will happen in 24 hours.',
		'NO_VERIFIED_DOMAIN'                     => 'Your merchant domain needs to be claimed.',
		'ADULT_INVALID'                          => 'Some items have invalid adult values.',
		'IMAGE_LINK_LENGTH_TOO_LONG'             => 'Some items have image_link URLs that contain too many characters, so those items will not be published.',
		'INVALID_DOMAIN'                         => 'Some of your product link values don\'t match the verified domain associated with this account.',
		'FEED_LENGTH_TOO_LONG'                   => 'Your feed contains too many items, some items will not be published.',
		'LINK_LENGTH_TOO_LONG'                   => 'Some product links contain too many characters, those items will not be published.',
		'MALFORMED_XML'                          => 'Your feed couldn\'t be validated because the xml file is formatted incorrectly.',
		'PRICE_MISSING'                          => 'Some products are missing a price, those items will not be published.',
		'FEED_TOO_SMALL'                         => 'Your feed couldn\'t be validated because the file doesn\'t contain the minimum number of lines required.',
		'MAX_ITEMS_PER_ITEM_GROUP_EXCEEDED'      => 'Some items exceed the maximum number of items per item group, those items will not be published.',
		'ITEM_MAIN_IMAGE_DOWNLOAD_FAILURE'       => 'Some items\' main images can\'t be found.',
		'PINJOIN_CONTENT_UNSAFE'                 => 'Some items were not published because they don\'t meet Pinterest\'s Merchant Guidelines.',
		'BLOCKLISTED_IMAGE_SIGNATURE'            => 'Some items were not published because they don\'t meet Pinterest\'s Merchant Guidelines.',
		'LIST_PRICE_INVALID'                     => 'Some items have list price formatting errors in their product metadata, those items will not be published.',
		'PRICE_CANNOT_BE_DETERMINED'             => 'Some items were not published because price cannot be determined. The price, list price, and sale price are all different, so those items will not be published.',

		// Validation warnings.
		'AD_LINK_FORMAT_WARNING'                 => 'Some items have ad links that are formatted incorrectly.',
		'AD_LINK_SAME_AS_LINK'                   => 'Some items have ad link URLs that are duplicates of the link URLs for those items.',
		'TITLE_LENGTH_TOO_LONG'                  => 'The title for some items were truncated because they contain too many characters.',
		'DESCRIPTION_LENGTH_TOO_LONG'            => 'The description for some items were truncated because they contain too many characters.',
		'GENDER_INVALID'                         => 'Some items have gender values that are formatted incorrectly, which may limit visibility in recommendations, search results and shopping experiences.',
		'AGE_GROUP_INVALID'                      => 'Some items have age group values that are formatted incorrectly, which may limit visibility in recommendations, search results and shopping experiences.',
		'SIZE_TYPE_INVALID'                      => 'Some items have size type values that are formatted incorrectly, which may limit visibility in recommendations, search results and shopping experiences.',
		'SIZE_SYSTEM_INVALID'                    => 'Some items have size system values which are not one of the supported size systems.',
		'LINK_FORMAT_WARNING'                    => 'Some items have an invalid product link which contains invalid UTM tracking paramaters.',
		'SALES_PRICE_INVALID'                    => 'Some items have sale price values that are higher than the original price of the item.',
		'PRODUCT_CATEGORY_DEPTH_WARNING'         => 'Some items only have 1 or 2 levels of google_product_category values, which may limit visibility in recommendations, search results and shopping experiences.',
		'ADWORDS_FORMAT_WARNING'                 => 'Some items have adwords_redirect links that are formatted incorrectly.',
		'ADWORDS_SAME_AS_LINK'                   => 'Some items have adwords_redirect URLs that are duplicates of the link URLs for those items.',
		'DUPLICATE_HEADERS'                      => 'Your feed contains duplicate headers.',
		'FETCH_SAME_SIGNATURE'                   => 'Ingestion completed early because there are no changes to your feed since the last successful update.',
		'ADDITIONAL_IMAGE_LINK_LENGTH_TOO_LONG'  => 'Some items have additional_image_link URLs that contain too many characters, so those items will not be published.',
		'ADDITIONAL_IMAGE_LINK_WARNING'          => 'Some items have additional_image_link URLs that are formatted incorrectly and will not be published with your items.',
		'IMAGE_LINK_WARNING'                     => 'Some items have image_link URLs that are formatted incorrectly and will not be published with those items.',
		'SHIPPING_INVALID'                       => 'Some items have shipping values that are formatted incorrectly.',
		'TAX_INVALID'                            => 'Some items have tax values that are formatted incorrectly.',
		'SHIPPING_WEIGHT_INVALID'                => 'Some items have invalid shipping_weight values.',
		'EXPIRATION_DATE_INVALID'                => 'Some items have expiration_date values that are formatted incorrectly, those items will be published without an expiration date.',
		'AVAILABILITY_DATE_INVALID'              => 'Some items have availability_date values that are formatted incorrectly, those items will be published without an availability date.',
		'SALE_DATE_INVALID'                      => 'Some items have sale_price_effective_date values that are formatted incorrectly, those items will be published without a sale date.',
		'WEIGHT_UNIT_INVALID'                    => 'Some items have weight_unit values that are formatted incorrectly, those items will be published without a weight unit.',
		'IS_BUNDLE_INVALID'                      => 'Some items have is_bundle values that are formatted incorrectly, those items will be published without being bundled with other products.',
		'UPDATED_TIME_INVALID'                   => 'Some items have updated_time values that are formatted incorrectly, those items will be published without an updated time.',
		'CUSTOM_LABEL_LENGTH_TOO_LONG'           => 'Some items have custom_label values that are too long, those items will be published without that custom label.',
		'PRODUCT_TYPE_LENGTH_TOO_LONG'           => 'Some items have product_type values that are too long, those items will be published without that product type.',
		'TOO_MANY_ADDITIONAL_IMAGE_LINKS'        => 'Some items have additional_image_link values that exceed the limit for additional images, those items will be published without some of your images.',
		'MULTIPACK_INVALID'                      => 'Some items have invalid multipack values.',
		'INDEXED_PRODUCT_COUNT_LARGE_DELTA'      => 'The product count has increased or decreased significantly compared to the last successful ingestion.',
		'ITEM_ADDITIONAL_IMAGE_DOWNLOAD_FAILURE' => 'Some items include additional_image_links that can\'t be found.',
		'OPTIONAL_PRODUCT_CATEGORY_MISSING'      => 'Some items are missing a google_product_category.',
		'OPTIONAL_PRODUCT_CATEGORY_INVALID'      => 'Some items include google_product_category values that are not formatted correctly according to the GPC taxonomy.',
		'OPTIONAL_CONDITION_MISSING'             => 'Some items are missing a condition value, which may limit visibility in recommendations, search results and shopping experiences.',
		'OPTIONAL_CONDITION_INVALID'             => 'Some items include condition values that are formatted incorrectly, which may limit visibility in recommendations, search results and shopping experiences.',
		'IOS_DEEP_LINK_INVALID'                  => 'Some items include invalid ios_deep_link values.',
		'ANDROID_DEEP_LINK_INVALID'              => 'Some items include invalid android_deep_link.',
		'UTM_SOURCE_AUTO_CORRECTED'              => 'Some items include utm_source values that are formatted incorrectly and have been automatically corrected.',
		'COUNTRY_DOES_NOT_MAP_TO_CURRENCY'       => 'Some items include a currency that doesn\'t match the usual currency for the location where that product is sold or shipped.',
		'MIN_AD_PRICE_INVALID'                   => 'Some items include min_ad_price values that are formatted incorrectly.',
		'GTIN_INVALID'                           => 'Some items include incorrectly formatted GTINs.',
		'INCONSISTENT_CURRENCY_VALUES'           => 'Some items include inconsistent currencies in price fields.',
		'SALES_PRICE_TOO_LOW'                    => 'Some items include sales price that is much lower than the list price.',
		'SHIPPING_WIDTH_INVALID'                 => 'Some items include incorrectly formatted shipping_width.',
		'SHIPPING_HEIGHT_INVALID'                => 'Some items include incorrectly formatted shipping_height.',
		'SALES_PRICE_TOO_HIGH'                   => 'Some items include a sales price that is higher than the list price. The sales price has been defaulted to the list price.',
		'MPN_INVALID'                            => 'Some items include incorrectly formatted MPNs.',

		// Ingestion errors.
		'LINE_LEVEL_INTERNAL_ERROR'              => 'We experienced a technical difficulty and were unable to ingest this some items. The next ingestion will happen in 24 hours.',
		'LARGE_PRODUCT_COUNT_DECREASE'           => 'The product count has decreased by more than 99% compared to the last successful ingestion.',
		'ACCOUNT_FLAGGED'                        => 'We detected an issue with your account and are not currently ingesting your items. Please review our policies at policy.pinterest.com/community-guidelines#section-spam or contact us at help.pinterest.com/contact for more information.',
		'IMAGE_LEVEL_INTERNAL_ERROR'             => 'We experienced a technical difficulty and were unable to download some images. The next download attempt will happen in 24 hours.',
		'IMAGE_FILE_NOT_ACCESSIBLE'              => 'Image files are unreadable. Please upload new files to continue.',
		'IMAGE_MALFORMED_URL'                    => 'Image files are unreadable. Please check your link and upload new files to continue.',
		'IMAGE_FILE_NOT_FOUND'                   => 'Image files are unreadable. Please upload new files to continue.',
		'IMAGE_INVALID_FILE'                     => 'Image files are unreadable. Please upload new files to continue.',

		// Ingestion warnings.
		'ADDITIONAL_IMAGE_LEVEL_INTERNAL_ERROR'  => 'We experienced a technical difficulty and were unable to download some additional images. The next download attempt will happen in 24 hours.',
		'ADDITIONAL_IMAGE_FILE_NOT_ACCESSIBLE'   => 'Additional image files are unreadable. Please upload new files to continue.',
		'ADDITIONAL_IMAGE_MALFORMED_URL'         => 'Additional image files are unreadable. Please check your link and upload new files to continue.',
		'ADDITIONAL_IMAGE_FILE_NOT_FOUND'        => 'Additional image files are unreadable. Please upload new files to continue.',
		'ADDITIONAL_IMAGE_INVALID_FILE'          => 'Additional image files are unreadable. Please upload new files to continue.',
		'HOTEL_PRICE_HEADER_IS_PRESENT'          => 'Price is not a supported column. Use base_price and sale_price instead.',

		// Ingestion info.
		'IN_STOCK'                               => 'The number of ingested products that are in stock.',
		'OUT_OF_STOCK'                           => 'The number of ingested products that are in out of stock.',
		'PREORDER'                               => 'The number of ingested products that are in preorder.',
	);

	const FEED_STATUS_NOT_REGISTERED = 'not_registered';

	const FEED_STATUS_ERROR_FETCHING_FEED = 'error_fetching_feed';

	const FEED_STATUS_COMPLETED = 'completed';

	const FEED_STATUS_COMPLETED_EARLY = 'completed_early';

	const FEED_STATUS_DISAPPROVED = 'disapproved';

	const FEED_STATUS_FAILED = 'failed';

	const FEED_STATUS_PROCESSING = 'processing';

	const FEED_STATUS_QUEUED_FOR_PROCESSING = 'queued_for_processing';

	const FEED_STATUS_UNDER_APPEAL = 'under_appeal';

	const FEED_STATUS_UNDER_REVIEW = 'under_review';

	/**
	 * Get the feed registration status.
	 *
	 * @return string The feed registration state. Possible values:
	 *                - not_registered: Feed is not yet configured on Pinterest.
	 *                - error_fetching_feed: Could not get feed info from Pinterest.
	 *                - inactive: The feed is registered but inactive at Pinterest.
	 *                - active: The feed is registered and active at Pinterest.
	 *                - deleted: The feed is registered but marked as deleted at Pinterest.
	 */
	public static function get_feed_registration_status(): string {
		$feed_id = FeedRegistration::get_locally_stored_registered_feed_id();

		if ( empty( $feed_id ) ) {
			return static::FEED_STATUS_NOT_REGISTERED;
		}

		$feed = Feeds::get_feed_recent_processing_results( $feed_id );

		if ( empty( $feed ) ) {
			return static::FEED_STATUS_NOT_REGISTERED;
		}

		return strtolower( $feed['status'] ) ?? static::FEED_STATUS_NOT_REGISTERED;
	}

	/**
	 * Get the sync process / feed ingestion status via Pinterest API.
	 *
	 * @return string The feed ingestion state. Possible values:
	 *                - not_registered: Feed is not registered with Pinterest.
	 *                - error_fetching_feed: Error when trying to get feed report from Pinterest.
	 *                - no_workflows: Feed report contains no feed workflow.
	 *                - completed: Feed automatically pulled by Pinterest / Feed ingestion completed.
	 *                - completed_early: Feed automatically pulled by Pinterest / Feed ingestion completed early.
	 *                - processing: Feed ingestion is processing.
	 *                - under_review: Feed is under review.
	 *                - queued_for_processing: Feed is queued for processing.
	 *                - failed: Feed ingestion failed.
	 *                - unknown: Unknown feed ingestion status in workflow (i.e. API returned an unknown status).
	 *
	 * @throws Exception PHP Exception.
	 */
	public static function get_feed_sync_status(): string {
		$ad_account_id = Pinterest_For_WooCommerce()::get_setting( 'tracking_advertiser' );
		$feed_id       = FeedRegistration::get_locally_stored_registered_feed_id();

		if ( empty( $ad_account_id ) || empty( $feed_id ) ) {
			throw new Exception( 'not_registered' );
		}

		try {
			try {
				$feed_results = Feeds::get_feed_recent_processing_results( $feed_id );
			} catch ( Exception $e ) {
				throw new Exception( 'error_fetching_feed' );
			}
			if ( empty( $feed_results ) ) {
				throw new Exception( 'no_workflows' );
			}

			$status = strtolower( $feed_results['status'] ?? '' );
			if ( ! in_array(
				$status,
				array(
					'completed',
					'completed_early',
					'processing',
					'under_review',
					'queued_for_processing',
					'failed',
				),
				true
			) ) {
				throw new Exception( 'unknown' );
			}
		} catch ( Exception $e ) {
			$status = $e->getMessage();
		}

		return $status;
	}

	/**
	 * Gets the overview totals from the given processing results array.
	 *
	 * @param   array $processing_results The processing results array.
	 * @return  array A multidimensional array of numbers indicating the following stats about the workflow:
	 *                  - total: The total number of products in the feed.
	 *                  - not_synced: The number of products not synced to Pinterest.
	 *                  - warnings: The number of warnings.
	 *                  - errors: The number of errors.
	 *
	 * @since 1.4.0
	 */
	public static function get_processing_result_overview_stats( array $processing_results ): array {
		$sums = array(
			'errors'   => 0,
			'warnings' => 0,
		);

		$sums['errors'] += array_sum( $processing_results['ingestion_details']['errors'] ?? array() );
		$sums['errors'] += array_sum( $processing_results['validation_details']['errors'] ?? array() );

		$sums['warnings'] += array_sum( $processing_results['ingestion_details']['warnings'] ?? array() );
		$sums['warnings'] += array_sum( $processing_results['validation_details']['warnings'] ?? array() );

		$original = $processing_results['product_counts']['original'] ?? 0;
		$ingested = $processing_results['product_counts']['ingested'] ?? 0;

		return array(
			'total'      => $original,
			'not_synced' => $original - $ingested,
			'warnings'   => $sums['warnings'],
			'errors'     => $sums['errors'],
		);
	}

	/**
	 * Gets the global error code for the given processing results.
	 *
	 * @param array $processing_results Recent processing results array.
	 * @return string
	 *
	 * @since 1.4.0
	 */
	public static function get_processing_results_global_error( array $processing_results ): string {
		$error_code = '';

		foreach ( $processing_results['validation_details']['errors'] as $error_code => $count ) {
			if ( in_array( $error_code, self::GLOBAL_ERROR_CODES, true ) ) {
				/* Translators: The error message as returned by the Pinterest API */
				return sprintf( esc_html__( 'Pinterest says: %1$s', 'pinterest-for-woocommerce' ), static::ERROR_MESSAGES[ $error_code ] ?? '' );
			}
		}

		foreach ( $processing_results['ingestion_details']['errors'] as $error_code => $count ) {
			if ( in_array( $error_code, self::GLOBAL_ERROR_CODES, true ) ) {
				/* Translators: The error message as returned by the Pinterest API */
				return sprintf( esc_html__( 'Pinterest says: %1$s', 'pinterest-for-woocommerce' ), static::ERROR_MESSAGES[ $error_code ] ?? '' );
			}
		}

		return '';
	}

	/**
	 * Log the full failed feed processing result context once per feed and processing result ID.
	 *
	 * @param string $feed_id            Pinterest feed ID.
	 * @param array  $processing_results Recent processing results array.
	 *
	 * @return void
	 */
	public static function log_failed_processing_result( string $feed_id, array $processing_results ): void {
		if ( Feeds::FEED_PROCESSING_STATUS_FAILED !== ( $processing_results['status'] ?? '' ) ) {
			return;
		}

		$processing_result_id = $processing_results['id'] ?? '';

		if ( empty( $processing_result_id ) ) {
			return;
		}

		$last_logged_processing_result_ids = Pinterest_For_WooCommerce()::get_data( self::LAST_LOGGED_PROCESSING_RESULT_ID_KEY );
		if ( ! is_array( $last_logged_processing_result_ids ) ) {
			$last_logged_processing_result_ids = array();
		}
		if ( ( $last_logged_processing_result_ids[ $feed_id ] ?? '' ) === $processing_result_id ) {
			return;
		}

		if ( ! function_exists( 'wc_get_logger' ) ) {
			return;
		}

		$feed_url = self::get_feed_url_for_feed_id( $feed_id );

		Logger::log(
			sprintf(
				"Feed ingestion FAILED\nfeed_id: %s\nprocessing_result_id: %s\ncreated_at: %s\nfeed_url: %s\nvalidation_details: %s\nproduct_counts: %s",
				$feed_id,
				$processing_result_id,
				$processing_results['created_at'] ?? '',
				$feed_url,
				wp_json_encode( $processing_results['validation_details'] ?? array() ),
				wp_json_encode( $processing_results['product_counts'] ?? array() )
			),
			'error',
			'feed-ingestion-failure'
		);

		$last_logged_processing_result_ids[ $feed_id ] = $processing_result_id;
		Pinterest_For_WooCommerce()::save_data(
			self::LAST_LOGGED_PROCESSING_RESULT_ID_KEY,
			$last_logged_processing_result_ids
		);
	}

	/**
	 * Get the registered feed URL (location) for a Pinterest feed ID.
	 *
	 * The feed ID is the remote Pinterest feed ID, so it is resolved against the
	 * registered remote feeds. Returns an empty string when no remote feed matches
	 * (e.g. the feeds API is unavailable).
	 *
	 * @param string $feed_id Pinterest feed ID.
	 * @return string
	 */
	private static function get_feed_url_for_feed_id( string $feed_id ): string {
		$feeds = Feeds::get_feeds();

		foreach ( $feeds as $feed ) {
			if ( ( $feed['id'] ?? '' ) === $feed_id ) {
				return $feed['location'] ?? '';
			}
		}

		// Distinguish "no registered feeds returned" (e.g. feeds API unavailable) from
		// "feeds returned but none match" (e.g. a stale or remote-only feed ID) so the
		// empty feed_url in the failure log can be diagnosed.
		Logger::log(
			empty( $feeds )
				? "Could not resolve feed_url: no registered feeds returned for feed_id={$feed_id}"
				: "Could not resolve feed_url: no registered feed matched feed_id={$feed_id}",
			'warning',
			'feed-ingestion-failure'
		);

		return '';
	}

	/**
	 * Retry Pinterest feed ingestion once when a transient FETCH_ERROR failure is observed.
	 *
	 * @param string $feed_id            Pinterest feed ID.
	 * @param array  $processing_results Recent processing results array.
	 *
	 * @return bool Whether a retry was triggered successfully.
	 */
	public static function maybe_retry_on_fetch_error( string $feed_id, array $processing_results ): bool {
		if ( Feeds::FEED_PROCESSING_STATUS_FAILED !== ( $processing_results['status'] ?? '' ) ) {
			return false;
		}

		if ( ! isset( $processing_results['validation_details']['errors']['FETCH_ERROR'] ) ) {
			return false;
		}

		$processing_result_id = $processing_results['id'] ?? '';
		if ( empty( $processing_result_id ) ) {
			return false;
		}

		$last_retried_processing_result_id = Pinterest_For_WooCommerce()::get_data(
			self::LAST_RETRIED_PROCESSING_RESULT_ID_KEY
		);
		if ( $processing_result_id === $last_retried_processing_result_id ) {
			return false;
		}

		try {
			Feeds::reschedule_feed_fetch( $feed_id );
			Pinterest_For_WooCommerce()::save_data( self::LAST_RETRIED_PROCESSING_RESULT_ID_KEY, $processing_result_id );
			Logger::log(
				"FETCH_ERROR retry triggered for processing_result_id={$processing_result_id}",
				'info',
				'feed-ingestion-failure'
			);

			return true;
		} catch ( Throwable $th ) {
			Logger::log(
				sprintf(
					'FETCH_ERROR retry failed for processing_result_id=%s: %s (code %s)',
					$processing_result_id,
					$th->getMessage(),
					$th->getCode()
				),
				'error',
				'feed-ingestion-failure'
			);

			return false;
		}
	}
}