Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 18.119.117.77
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/wavevlvu/diixadigital.com/wp-content/plugins/pinterest-for-woocommerce/src/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/wavevlvu/diixadigital.com/wp-content/plugins/pinterest-for-woocommerce/src/FeedGenerator.php
<?php //phpcs:disable WordPress.WP.AlternativeFunctions --- Uses FS read/write in order to reliably append to an existing file.
/**
 * Pinterest for WooCommerce Feed Files Generator
 *
 * @package     Pinterest_For_WooCommerce/Classes/
 * @since       1.0.10
 */

namespace Automattic\WooCommerce\Pinterest;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Automattic\WooCommerce\ActionSchedulerJobFramework\Utilities\BatchQueryOffset;
use Automattic\WooCommerce\ActionSchedulerJobFramework\AbstractChainedJob;
use Automattic\WooCommerce\ActionSchedulerJobFramework\Proxies\ActionSchedulerInterface;
use Automattic\WooCommerce\Pinterest\Exception\FeedFileOperationsException;
use Automattic\WooCommerce\Pinterest\Utilities\ProductFeedLogger;
use ActionScheduler;
use Exception;
use Pinterest_For_Woocommerce;
use Throwable;

/**
 * Class Handling feed files generation.
 */
class FeedGenerator extends AbstractChainedJob {

	use BatchQueryOffset;
	use ProductFeedLogger;

	const ACTION_START_FEED_GENERATOR = PINTEREST_FOR_WOOCOMMERCE_PREFIX . '-start-feed-generation';

	/**
	 * The time in seconds to wait after a failed feed generation attempt,
	 * before attempting a retry.
	 */
	const WAIT_ON_ERROR_BEFORE_RETRY = HOUR_IN_SECONDS;

	/**
	 * The max number of retries per batch before aborting the generation process.
	 */
	const MAX_RETRIES_PER_BATCH = 2;

	public const DEFAULT_PRODUCT_BATCH_SIZE = 100;

	/**
	 * Feed file operations class.
	 *
	 * @var FeedFileOperations
	 */
	private $feed_file_operations;

	/**
	 * Local Feed Configurations class.
	 *
	 * @var LocalFeedConfigs of local feed configurations;
	 */
	private $configurations;

	/**
	 * Location buffers. On buffer for each local feed configuration.
	 * We write to a buffer to limit the number disk writes.
	 *
	 * @var array $buffers Array of feed buffers.
	 */
	private $buffers = array();

	/**
	 * FeedGenerator initialization.
	 *
	 * @since 1.0.10
	 * @param ActionSchedulerInterface $action_scheduler           Action Scheduler proxy.
	 * @param FeedFileOperations       $feed_file_operations       Feed file operations.
	 * @param LocalFeedConfigs         $local_feeds_configurations Locations configuration class.
	 */
	public function __construct( ActionSchedulerInterface $action_scheduler, FeedFileOperations $feed_file_operations, $local_feeds_configurations ) {
		parent::__construct( $action_scheduler );
		$this->feed_file_operations = $feed_file_operations;
		$this->configurations       = $local_feeds_configurations;
	}

	/**
	 * Initialize FeedGenerator actions and Action Scheduler hooks.
	 *
	 * @since 1.0.10
	 */
	public function init() {
		// Initialize the action handlers.
		parent::init();

		add_action(
			self::ACTION_START_FEED_GENERATOR,
			function () {
				$this->start_generation();
			}
		);

		if ( false === as_has_scheduled_action( self::ACTION_START_FEED_GENERATOR, array(), PINTEREST_FOR_WOOCOMMERCE_PREFIX ) ) {
			$this->schedule_next_generator_start( time() );
		}

		// Set the store address as taxable location.
		add_filter( 'woocommerce_customer_taxable_address', array( $this, 'set_store_address_as_taxable_location' ) );

		// PHP shuts down execution for some reason.
		add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'handle_unexpected_shutdown' ), 10, 2 );
		// Action got an exception thrown.
		add_action( 'action_scheduler_failed_execution', array( $this, 'handle_failed_execution' ), 10, 2 );
	}

	/**
	 * Returns feed generator actions group name.
	 *
	 * @since 1.3.1
	 *
	 * @return string
	 */
	public function get_group_name(): string {
		return PINTEREST_FOR_WOOCOMMERCE_PREFIX;
	}

	/**
	 * Unexpected shutdown handler.
	 *
	 * @param int        $action_id - Action Scheduler action ID.
	 * @param array|null $error - Error details.
	 *
	 * @since 1.3.1
	 *
	 * @return void
	 */
	public function handle_unexpected_shutdown( int $action_id, ?array $error ) {
		if ( ! $error || ! $this->is_timeout_error( $error ) ) {
			return;
		}
		$action = ActionScheduler::store()->fetch_action( $action_id );
		$hook   = $action->get_hook();
		$args   = $action->get_args();

		// If not Pinterest Feed Generator action - ignore.
		if ( $hook !== $this->get_action_full_name( self::CHAIN_BATCH ) ) {
			return;
		}

		// Check if the action had failed before.
		if ( $this->is_failure_rate_above_threshold( $hook, $args ) ) {
			self::log(
				sprintf(
					// Translators: 1. Action Scheduler hook name.
					__(
						'Feed Generator `%s` Action reschedule threshold has been reached. Quit.',
						'pinterest-for-woocommerce'
					),
					$hook
				)
			);
			return;
		}

		self::log(
			sprintf(
				// Translators: Action Scheduler hook name.
				__(
					'Feed Generator `%s` Action timed out due to an unexpected shutdown. Rescheduling it.',
					'pinterest-for-woocommerce'
				),
				$hook
			)
		);

		// Decrease the number of products to retry.
		$attempt = ( Pinterest_For_Woocommerce::get_data( 'feed_product_batch_attempt' ) ?? 1 ) + 1;
		$limit   = (int) ceil( $this->get_batch_size() / $attempt );
		Pinterest_For_Woocommerce::save_data( 'feed_product_batch_size', $limit );
		Pinterest_For_Woocommerce::save_data( 'feed_product_batch_attempt', $attempt );

		self::log(
			sprintf(
				// Translators: 1: Action Scheduler hook name, 2: New products number to process next action run.
				__(
					'Feed Generator `%1$s` Action product batch size decreased to %2$d.',
					'pinterest-for-woocommerce'
				),
				$hook,
				$limit
			)
		);

		// Register retry attempt.
		$this->action_scheduler->schedule_immediate( $hook, $args, PINTEREST_FOR_WOOCOMMERCE_PREFIX );
	}

	/**
	 * Action exception handler.
	 *
	 * @param int       $action_id - Action Scheduler action id.
	 * @param Throwable $throwable - Exception object.
	 *
	 * @since 1.3.1
	 *
	 * @return void
	 */
	public function handle_failed_execution( int $action_id, Throwable $throwable ) {
		$action = ActionScheduler::store()->fetch_action( $action_id );
		$hook   = $action->get_hook();

		// If not Pinterest Feed Generator action - ignore.
		if ( $hook !== $this->get_action_full_name( self::CHAIN_BATCH ) ) {
			return;
		}

		$this->handle_error( $throwable, $hook );
	}

	/**
	 * Reschedule the next feed generator start.
	 *
	 * @since 1.0.10
	 * @param integer $timestamp Next feed generator timestamp.
	 */
	public function schedule_next_generator_start( $timestamp ) {
		as_unschedule_all_actions( self::ACTION_START_FEED_GENERATOR, array(), PINTEREST_FOR_WOOCOMMERCE_PREFIX );
		as_schedule_recurring_action( $timestamp, DAY_IN_SECONDS, self::ACTION_START_FEED_GENERATOR, array(), PINTEREST_FOR_WOOCOMMERCE_PREFIX );
		/* translators: time in the format hours:minutes:seconds */
		self::log( sprintf( __( 'Feed scheduled to run at %s.', 'pinterest-for-woocommerce' ), gmdate( 'H:i:s', $timestamp ) ) );
	}

	/**
	 * Stop feed generator jobs.
	 */
	public static function cancel_jobs() {
		as_unschedule_all_actions( self::ACTION_START_FEED_GENERATOR, array(), PINTEREST_FOR_WOOCOMMERCE_PREFIX );
	}

	/**
	 * Start the queue processing.
	 *
	 * @since 1.0.10
	 */
	private function start_generation() {
		if ( $this->is_running() ) {
			return;
		}

		$this->queue_start();
		ProductFeedStatus::set( array( 'status' => 'scheduled_for_generation' ) );
		self::log( __( 'Feed generation queued.', 'pinterest-for-woocommerce' ) );
	}

	/**
	 * Runs as the first step of the generation process.
	 *
	 * @since 1.0.10
	 *
	 * @throws Throwable Related to creating an empty feed temp file and populating the header possible issues.
	 */
	protected function handle_start() {
		self::log( __( 'Feed generation start. Preparing temporary files.', 'pinterest-for-woocommerce' ) );
		try {
			ProductFeedStatus::reset_feed_file_generation_time();
			ProductFeedStatus::set(
				array(
					'status'        => 'in_progress',
					'product_count' => 0,
				)
			);
			$this->feed_file_operations->prepare_temporary_files();
		} catch ( Throwable $th ) {
			$this->handle_error( $th, $this->get_action_full_name( self::CHAIN_START ) );
			throw $th;
		}
	}

	/**
	 * Handle processing a chain batch.
	 *
	 * @since 1.2.14
	 *
	 * @param int   $batch_number The batch number for the new batch.
	 * @param array $args         The args for the job.
	 *
	 * @throws Throwable Related to issue possible when creating an empty feed temp file and populating the header.
	 */
	public function handle_batch_action( int $batch_number, array $args ) {
		parent::handle_batch_action( $batch_number, $args );

		/*
		 * Action has finished successfully.
		 *   - Reset number of products per batch.
		 *   - Reset action retries counter.
		 */
		Pinterest_For_Woocommerce::remove_data( 'feed_product_batch_size' );
		Pinterest_For_Woocommerce::remove_data( 'feed_product_batch_attempt' );
	}

	/**
	 * Runs as the last step of the job.
	 * Add XML footer to the feed files and copy the move the files from tmp to the final destination.
	 *
	 * @since 1.0.10
	 *
	 * @throws Throwable Related to adding the footer or renaming the files possible issues.
	 */
	protected function handle_end() {
		self::log( __( 'Feed generation end. Moving files to the final destination.', 'pinterest-for-woocommerce' ) );
		try {
			$this->feed_file_operations->add_footer_to_temporary_feed_files();
			$this->feed_file_operations->rename_temporary_feed_files_to_final();
			ProductFeedStatus::set(
				array(
					'status' => 'generated',
					ProductFeedStatus::PROP_FEED_GENERATION_RECENT_PRODUCT_COUNT => ProductFeedStatus::get()['product_count'],
				)
			);
			ProductFeedStatus::set_feed_file_generation_time( time() );
		} catch ( Throwable $th ) {
			$this->handle_error( $th, $this->get_action_full_name( self::CHAIN_END ) );
			throw $th;
		}
		self::log( __( 'Feed generated successfully.', 'pinterest-for-woocommerce' ) );

		// Check if feed is dirty and reschedule in necessary.
		if ( $this->feed_is_dirty() ) {
			$this->mark_feed_clean();
			$this->schedule_next_generator_start( time() );
		}
	}

	/**
	 * Get a set of items for the batch.
	 *
	 * NOTE: when using an OFFSET based query to retrieve items it's recommended to order by the item ID while
	 * ASCENDING. This is so that any newly added items will not disrupt the query offset.
	 *
	 * @param int   $batch_number The batch number increments for each new batch in the job cycle.
	 * @param array $args         The args for the job.
	 *
	 * @return array Items ids.
	 *
	 * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop.
	 */
	protected function get_items_for_batch( int $batch_number, array $args ): array {
		global $wpdb;

		$product_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT post.ID
				FROM {$wpdb->posts} as post
				LEFT JOIN {$wpdb->posts} as parent ON post.post_parent = parent.ID
				WHERE
					(
						( post.post_type = 'product_variation' AND parent.post_status = 'publish' )
					OR
						( post.post_type = 'product' AND post.post_status = 'publish' )
					)
				AND
					post.ID > %d
				ORDER BY post.ID ASC
				LIMIT %d",
				$this->get_last_batch_id( $batch_number ),
				$this->get_batch_size()
			)
		);

		$product_ids = array_map( 'intval', $product_ids );
		// We save the last product's id from the current batch to start from it next time when fetching the next batch.
		$this->set_last_batch_id( $product_ids[ count( $product_ids ) - 1 ] ?? 0 );
		return $product_ids;
	}

	/**
	 * Processes a batch of items. The middle part of the generation process.
	 * Can run multiple times depending on the catalog size.
	 *
	 * @since 1.0.10
	 *
	 * @param array $items The items of the current batch.
	 * @param array $args  The args for the job.
	 *
	 * @throws FeedFileOperationsException In case there was an exception thrown when writing to a feed file.
	 */
	protected function process_items( array $items, array $args ) {
		$products = $this->get_feed_products( $items );

		$this->prepare_feed_buffers();

		$processed_products = 0;
		foreach ( $products as $product ) {
			foreach ( $this->get_locations() as $location ) {
				$product_xml = ProductsXmlFeed::get_xml_item( $product, $location );
				if ( '' === $product_xml ) {
					continue;
				}
				$this->buffers[ $location ] .= $product_xml;
			}
			++$processed_products;
		}

		// May throw write to file exception.
		$this->feed_file_operations->write_buffers_to_temp_files( $this->buffers );

		$count = ProductFeedStatus::get()['product_count'] ?? 0;
		ProductFeedStatus::set(
			array(
				'product_count' => $count + $processed_products,
			)
		);
		/* translators: number of products */
		self::log( sprintf( __( 'Feed batch generated. Wrote %s products to the feed file.', 'pinterest-for-woocommerce' ), $processed_products ) );
	}

	/**
	 * Returns WC products by product ids. Products returned are of either `in stock` or `on backorder` statuses.
	 *
	 * @since 1.2.19
	 *
	 * @param int[] $ids - array of product ids.
	 *
	 * @return array|\stdClass
	 */
	public function get_feed_products( array $ids ) {
		// Get included product types.
		$included_product_types = array_diff(
			self::get_included_product_types(),
			self::get_excluded_product_types(),
		);

		$products_query_args = array(
			'type'       => $included_product_types,
			'include'    => $ids,
			'visibility' => 'catalog',
			'orderby'    => 'none',
			'limit'      => $this->get_batch_size(),
		);

		// Exclude variation subscriptions.
		$products_query_args['parent_exclude'] = $this->get_excluded_products_by_parent();

		// Do not sync out of stock products which do not support backorders if woocommerce_hide_out_of_stock_items is set.
		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$products_query_args['stock_status'] = [ 'instock', 'onbackorder' ];
		}

		return wc_get_products( $products_query_args );
	}

	/**
	 * Get the name/slug of the job.
	 *
	 * @return string
	 */
	public function get_name(): string {
		return 'generate_feed';
	}

	/**
	 * Get the name/slug of the plugin that owns the job.
	 *
	 * @return string
	 */
	public function get_plugin_name(): string {
		return 'pinterest';
	}

	/**
	 * Marks feed as dirty.
	 *
	 * @since 1.0.10
	 */
	public function mark_feed_dirty(): void {
		Pinterest_For_Woocommerce()::save_data( 'feed_dirty', true );
		self::log( 'Feed is dirty.' );

		if ( $this->is_running() ) {
			// New generation will be started at the end of current one.
			return;
		}

		// Start new feed generation cycle now.
		$this->schedule_next_generator_start( time() );
	}

	/**
	 * Marks feed as clean.
	 *
	 * @since 1.0.10
	 */
	public function mark_feed_clean(): void {
		Pinterest_For_Woocommerce()::save_data( 'feed_dirty', false );
	}

	/**
	 * Check if feed is dirty.
	 *
	 * @since 1.0.10
	 * @return bool Indicates if feed is dirty or not.
	 */
	public function feed_is_dirty(): bool {
		return (bool) Pinterest_For_Woocommerce()::get_data( 'feed_dirty' );
	}

	/**
	 * @param Throwable $th - An exception that was thrown.
	 * @param string    $hook_name - The name of the hook that was being executed when the exception was thrown.
	 *
	 * @since 1.0.10
	 *
	 * @return void
	 */
	private function handle_error( Throwable $th, string $hook_name = '' ) {
		ProductFeedStatus::set(
			array(
				'status'        => 'error',
				'error_message' => $th->getMessage(),
			)
		);
		ProductFeedStatus::mark_feed_file_generation_as_failed();

		self::log(
			sprintf(
				// Translators: 1: Action Scheduler hook name, 2: Error message about why action has failed to execute.
				__(
					'Feed Generator `%1$s` Action failed to execute due to an error thrown `%2$s.`. A complete feed generation retry has been scheduled.',
					'pinterest-for-woocommerce'
				),
				$hook_name,
				$th->getMessage()
			),
			\WC_Log_Levels::ERROR
		);

		$this->schedule_next_generator_start( time() + self::WAIT_ON_ERROR_BEFORE_RETRY );
	}

	/**
	 * Remove feed files and cancel pending actions.
	 * Part of the cleanup procedure.
	 *
	 * @since 1.0.10
	 */
	public static function deregister(): void {
		foreach ( LocalFeedConfigs::get_instance()->get_configurations() as $config ) {
			if ( isset( $config['feed_file'] ) && file_exists( $config['feed_file'] ) ) {
				unlink( $config['feed_file'] );
			}

			if ( isset( $config['tmp_file'] ) && file_exists( $config['tmp_file'] ) ) {
				unlink( $config['tmp_file'] );
			}
		}
		as_unschedule_all_actions( self::ACTION_START_FEED_GENERATOR, array(), PINTEREST_FOR_WOOCOMMERCE_PREFIX );
	}

	/**
	 * Create empty string buffers for
	 *
	 * @since 1.0.10
	 */
	private function prepare_feed_buffers(): void {
		foreach ( $this->get_locations() as $location ) {
			$this->buffers[ $location ] = '';
		}
	}

	/**
	 * Fetch supported locations.
	 *
	 * @since 1.0.10
	 */
	private function get_locations(): array {
		return array_keys( $this->configurations->get_configurations() );
	}

	/**
	 * Get the job's batch size.
	 *
	 * @return int - The number of products to process per batch.
	 */
	protected function get_batch_size(): int {
		/**
		 * Returns products to process per batch.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		return Pinterest_For_Woocommerce::get_data( 'feed_product_batch_size' ) ?? apply_filters(
			PINTEREST_FOR_WOOCOMMERCE_OPTION_NAME . '_feed_product_batch_size',
			self::DEFAULT_PRODUCT_BATCH_SIZE
		);
	}

	/**
	 * Returns last product id from the last batch of products fetched at the previous step.
	 *
	 * @param int $batch_number - Action Scheduler chain action batch number.
	 * @return int
	 */
	protected function get_last_batch_id( int $batch_number ): int {
		if ( 1 === $batch_number ) {
			// Reset last fetched ID if batch number equals to 1.
			Pinterest_For_Woocommerce::save_data( 'feed_last_queued_item_id', 0 );
		}
		// Get last fetched ID to start from the next item after it.
		return Pinterest_For_Woocommerce::get_data( 'feed_last_queued_item_id' );
	}

	/**
	 * Saves last product id.
	 *
	 * @param int $id - product id.
	 * @return void
	 */
	protected function set_last_batch_id( int $id ): void {
		Pinterest_For_Woocommerce::save_data( 'feed_last_queued_item_id', $id );
	}

	/**
	 * Not used.
	 * Process a single item. Added to satisfy abstract interface definition in the framework.
	 * We use process_items instead.
	 *
	 * @param string|int|array $item A single item from the get_items_for_batch() method.
	 * @param array            $args The args for the job.
	 *
	 * @throws Exception On error. The failure will be logged by Action Scheduler and the job chain will stop.
	 */
	protected function process_item( $item, array $args ) {
		// Process each item here.
	}

	/**
	 * Return the list of supported product types.
	 *
	 * @since 1.2.9
	 *
	 * @return array
	 */
	private function get_included_product_types(): array {
		/**
		 * Returns array of included product types.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		return (array) apply_filters(
			'pinterest_for_woocommerce_included_product_types',
			array(
				'simple',
				'variation',
			)
		);
	}

	/**
	 * Return the list of excluded product types.
	 *
	 * @since 1.2.9
	 *
	 * @return array
	 */
	private function get_excluded_product_types(): array {
		/**
		 * Returns array of excluded product types.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		return (array) apply_filters(
			'pinterest_for_woocommerce_excluded_product_types',
			array(
				'grouped',
				'variable',
				'subscription',
				'variable-subscription',
			)
		);
	}

	/**
	 * Exclude products by parent (e.g. 'variation-subscriptions').
	 *
	 * @since 1.2.9
	 *
	 * @return array
	 */
	private function get_excluded_products_by_parent(): array {
		/**
		 * Returns array of excluded products by parent.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		return (array) apply_filters(
			'pinterest_for_woocommerce_excluded_products_by_parent',
			wc_get_products(
				array(
					'type'   => 'variable-subscription',
					'limit'  => -1,
					'return' => 'ids',
				)
			)
		);
	}

	/**
	 * Set the store address as taxable location.
	 *
	 * @since 1.2.13
	 *
	 * @param array $taxable_location The taxable location.
	 */
	public function set_store_address_as_taxable_location( array $taxable_location ) {

		if ( ! doing_action( $this->get_action_full_name( self::CHAIN_BATCH ) ) ) {
			return $taxable_location;
		}

		if ( isset( $taxable_location[0] ) ) {
			$taxable_location[0] = Pinterest_For_Woocommerce()::get_base_country();
		}

		return $taxable_location;
	}

	/**
	 * Determines whether the given error is an execution "timeout" error.
	 *
	 * @param array $error An associative array describing the error with keys "type", "message", "file" and "line".
	 *
	 * @return bool
	 *
	 * @since 1.2.14
	 */
	protected function is_timeout_error( array $error ): bool {
		$is_error              = E_ERROR === $error['type'] ?? 0;
		$is_max_execution_time = strpos( $error['message'] ?? '', 'Maximum execution time' ) !== false;
		return $is_error && $is_max_execution_time;
	}

	/**
	 * Handle error on generate feed timeout.
	 *
	 * @since 1.2.14
	 * @deprecated x.x.x
	 *
	 * @param int $action_id The ID of the action marked as failed.
	 *
	 * @throws Exception Related to max retries reached or missing arguments on the action.
	 */
	public function maybe_handle_error_on_timeout( int $action_id ) {
		wc_deprecated_function( __METHOD__, '1.3.5' );
	}

	/**
	 * Check whether the action's failure rate is above the specified threshold within the timeframe.
	 *
	 * @param string $hook The job action hook.
	 * @param ?array $args The job arguments.
	 *
	 * @return bool True if the action's error rate is above the threshold, and false otherwise.
	 *
	 * @since 1.3.1
	 */
	protected function is_failure_rate_above_threshold( string $hook, ?array $args = null ): bool {
		/**
		 * Threshold of failed actions.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		$threshold   = apply_filters( 'pinterest_for_woocommerce_action_failure_threshold', 3 );
		/**
		 * Time period of failed actions.
		 * phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
		 */
		$time_period = apply_filters( 'pinterest_for_woocommerce_action_failure_time_period', 30 * MINUTE_IN_SECONDS );
		$failed_actions = $this->action_scheduler->search(
			[
				'hook'         => $hook,
				'args'         => $args,
				'status'       => ActionSchedulerInterface::STATUS_FAILED,
				'date'         => gmdate( 'U' ) - $time_period,
				'date_compare' => '>',
			],
			'ids'
		);

		return count( $failed_actions ) >= $threshold;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit