File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/Jobs.tar
AbstractActionSchedulerJob.php 0000644 00000007104 15154275254 0012464 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractActionSchedulerJob
*
* Abstract class for jobs that use ActionScheduler.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractActionSchedulerJob implements ActionSchedulerJobInterface {
use PluginHelper;
/**
* @var ActionSchedulerInterface
*/
protected $action_scheduler;
/**
* @var ActionSchedulerJobMonitor
*/
protected $monitor;
/**
* Whether the job should be rescheduled on timeout.
*
* @var bool
*/
protected $retry_on_timeout = true;
/**
* AbstractActionSchedulerJob constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
*/
public function __construct( ActionSchedulerInterface $action_scheduler, ActionSchedulerJobMonitor $monitor ) {
$this->action_scheduler = $action_scheduler;
$this->monitor = $monitor;
}
/**
* Init the batch schedule for the job.
*
* The job name is used to generate the schedule event name.
*/
public function init(): void {
add_action( $this->get_process_item_hook(), [ $this, 'handle_process_items_action' ] );
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args );
}
/**
* Handles processing single item action hook.
*
* @hooked gla/jobs/{$job_name}/process_item
*
* @param array $items The job items from the current batch.
*
* @throws Exception If an error occurs.
*/
public function handle_process_items_action( array $items = [] ) {
$process_hook = $this->get_process_item_hook();
$process_args = [ $items ];
$this->monitor->validate_failure_rate( $this, $process_hook, $process_args );
if ( $this->retry_on_timeout ) {
$this->monitor->attach_timeout_monitor( $process_hook, $process_args );
}
try {
$this->process_items( $items );
} catch ( Exception $exception ) {
// reschedule on failure
$this->action_scheduler->schedule_immediate( $process_hook, $process_args );
// throw the exception again so that it can be logged
throw $exception;
}
$this->monitor->detach_timeout_monitor( $process_hook, $process_args );
}
/**
* Check if this job is running.
*
* The job is considered to be running if the "process_item" action is currently pending or in-progress.
*
* @param array|null $args
*
* @return bool
*/
protected function is_running( ?array $args = [] ): bool {
return $this->action_scheduler->has_scheduled_action( $this->get_process_item_hook(), $args );
}
/**
* Get the base name for the job's scheduled actions.
*
* @return string
*/
protected function get_hook_base_name(): string {
return "{$this->get_slug()}/jobs/{$this->get_name()}/";
}
/**
* Get the hook name for the "process item" action.
*
* This method is required by the job monitor.
*
* @return string
*/
public function get_process_item_hook(): string {
return "{$this->get_hook_base_name()}process_item";
}
/**
* Process batch items.
*
* @param array $items A single batch from the get_batch() method.
*
* @throws Exception If an error occurs. The exception will be logged by ActionScheduler.
*/
abstract protected function process_items( array $items );
}
AbstractBatchedActionSchedulerJob.php 0000644 00000013025 15154275255 0013737 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* AbstractBatchedActionSchedulerJob class.
*
* Enables a job to be processed in recurring scheduled batches with queued events.
*
* Notes:
* - Uses ActionScheduler's very scalable async actions feature which will run async batches in loop back requests until all batches are done
* - Items may be processed concurrently by AS, but batches will be created one after the other, not concurrently
* - The job will not start if it is already running
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractBatchedActionSchedulerJob extends AbstractActionSchedulerJob implements BatchedActionSchedulerJobInterface {
/**
* Init the batch schedule for the job.
*
* The job name is used to generate the schedule event name.
*/
public function init(): void {
add_action( $this->get_create_batch_hook(), [ $this, 'handle_create_batch_action' ] );
parent::init();
}
/**
* Get the hook name for the "create batch" action.
*
* @return string
*/
protected function get_create_batch_hook(): string {
return "{$this->get_hook_base_name()}create_batch";
}
/**
* Enqueue the "create_batch" action provided it doesn't already exist.
*
* To minimize the resource use of starting the job the batch creation is handled async.
*
* @param array $args
*/
public function schedule( array $args = [] ) {
$this->schedule_create_batch_action( 1 );
}
/**
* Handles batch creation action hook.
*
* @hooked gla/jobs/{$job_name}/create_batch
*
* Schedules an action to run immediately for the items in the batch.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @throws Exception If an error occurs.
* @throws JobException If the job failure rate is too high.
*/
public function handle_create_batch_action( int $batch_number ) {
$create_batch_hook = $this->get_create_batch_hook();
$create_batch_args = [ $batch_number ];
$this->monitor->validate_failure_rate( $this, $create_batch_hook, $create_batch_args );
if ( $this->retry_on_timeout ) {
$this->monitor->attach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
$items = $this->get_batch( $batch_number );
if ( empty( $items ) ) {
// if no more items the job is complete
$this->handle_complete( $batch_number );
} else {
// if items, schedule the process action
$this->schedule_process_action( $items );
// Add another "create_batch" action to handle unfiltered items.
// The last batch created here will be an empty batch, it
// will call "handle_complete" to finish the job.
$this->schedule_create_batch_action( $batch_number + 1 );
}
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
/**
* Get job batch size.
*
* @return int
*/
protected function get_batch_size(): int {
/**
* Filters the batch size for the job.
*
* @param string Job's name
*/
return apply_filters( 'woocommerce_gla_batched_job_size', 100, $this->get_name() );
}
/**
* Get the query offset based on a given batch number and the specified batch size.
*
* @param int $batch_number
*
* @return int
*/
protected function get_query_offset( int $batch_number ): int {
return $this->get_batch_size() * ( $batch_number - 1 );
}
/**
* Schedule a new "create batch" action to run immediately.
*
* @param int $batch_number The batch number for the new batch.
*/
protected function schedule_create_batch_action( int $batch_number ) {
if ( $this->can_schedule( [ $batch_number ] ) ) {
$this->action_scheduler->schedule_immediate( $this->get_create_batch_hook(), [ $batch_number ] );
}
}
/**
* Schedule a new "process" action to run immediately.
*
* @param int[] $items Array of item ids.
*/
protected function schedule_process_action( array $items ) {
if ( ! $this->is_processing( $items ) ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook(), [ $items ] );
}
}
/**
* Check if this job is running.
*
* The job is considered to be running if a "create_batch" action is currently pending or in-progress.
*
* @param array|null $args
*
* @return bool
*/
protected function is_running( ?array $args = [] ): bool {
return $this->action_scheduler->has_scheduled_action( $this->get_create_batch_hook(), $args );
}
/**
* Check if this job is processing the given items.
*
* The job is considered to be processing if a "process_item" action is currently pending or in-progress.
*
* @param array $items
*
* @return bool
*/
protected function is_processing( array $items = [] ): bool {
return $this->action_scheduler->has_scheduled_action( $this->get_process_item_hook(), [ $items ] );
}
/**
* Called when the job is completed.
*
* @param int $final_batch_number The final batch number when the job was completed.
* If equal to 1 then no items were processed by the job.
*/
protected function handle_complete( int $final_batch_number ) {
// Optionally over-ride this method in child class.
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*
* @throws Exception If an error occurs. The exception will be logged by ActionScheduler.
*/
abstract protected function get_batch( int $batch_number ): array;
}
AbstractCouponSyncerJob.php 0000644 00000003712 15154275255 0012041 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponSyncer;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractCouponSyncerJob
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractCouponSyncerJob extends AbstractActionSchedulerJob {
/**
* @var CouponHelper
*/
protected $coupon_helper;
/**
* @var CouponSyncer
*/
protected $coupon_syncer;
/**
* @var WC
*/
protected $wc;
/**
* @var MerchantCenterService
*/
protected $merchant_center;
/**
* AbstractCouponSyncerJob constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param CouponHelper $coupon_helper
* @param CouponSyncer $coupon_syncer
* @param WC $wc
* @param MerchantCenterService $merchant_center
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
CouponHelper $coupon_helper,
CouponSyncer $coupon_syncer,
WC $wc,
MerchantCenterService $merchant_center
) {
$this->coupon_helper = $coupon_helper;
$this->coupon_syncer = $coupon_syncer;
$this->wc = $wc;
$this->merchant_center = $merchant_center;
parent::__construct( $action_scheduler, $monitor );
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
}
}
AbstractProductSyncerBatchedJob.php 0000644 00000004701 15154275255 0013470 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\BatchProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncer;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractGoogleBatchedJob
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractProductSyncerBatchedJob extends AbstractBatchedActionSchedulerJob {
/**
* @var ProductSyncer
*/
protected $product_syncer;
/**
* @var ProductRepository
*/
protected $product_repository;
/**
* @var BatchProductHelper
*/
protected $batch_product_helper;
/**
* @var MerchantCenterService
*/
protected $merchant_center;
/**
* @var MerchantStatuses
*/
protected $merchant_statuses;
/**
* AbstractProductSyncerBatchedJob constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param ProductSyncer $product_syncer
* @param ProductRepository $product_repository
* @param BatchProductHelper $batch_product_helper
* @param MerchantCenterService $merchant_center
* @param MerchantStatuses $merchant_statuses
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
ProductSyncer $product_syncer,
ProductRepository $product_repository,
BatchProductHelper $batch_product_helper,
MerchantCenterService $merchant_center,
MerchantStatuses $merchant_statuses
) {
$this->batch_product_helper = $batch_product_helper;
$this->product_syncer = $product_syncer;
$this->product_repository = $product_repository;
$this->merchant_center = $merchant_center;
$this->merchant_statuses = $merchant_statuses;
parent::__construct( $action_scheduler, $monitor );
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
}
}
AbstractProductSyncerJob.php 0000644 00000003513 15154275255 0012215 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncer;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractProductSyncerJob
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractProductSyncerJob extends AbstractActionSchedulerJob {
/**
* @var ProductSyncer
*/
protected $product_syncer;
/**
* @var ProductRepository
*/
protected $product_repository;
/**
* @var MerchantCenterService
*/
protected $merchant_center;
/**
* AbstractProductSyncerJob constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param ProductSyncer $product_syncer
* @param ProductRepository $product_repository
* @param MerchantCenterService $merchant_center
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
ProductSyncer $product_syncer,
ProductRepository $product_repository,
MerchantCenterService $merchant_center
) {
$this->product_syncer = $product_syncer;
$this->product_repository = $product_repository;
$this->merchant_center = $merchant_center;
parent::__construct( $action_scheduler, $monitor );
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && $this->merchant_center->should_push();
}
}
ActionSchedulerJobInterface.php 0000644 00000001421 15154275255 0012616 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
defined( 'ABSPATH' ) || exit;
/**
* Interface ActionSchedulerJobInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
interface ActionSchedulerJobInterface extends JobInterface {
/**
* Get the hook name for the "process item" action.
*
* This method is required by the job monitor.
*
* @return string
*/
public function get_process_item_hook(): string;
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool;
/**
* Schedule the job.
*
* @param array $args
*/
public function schedule( array $args = [] );
}
ActionSchedulerJobMonitor.php 0000644 00000015610 15154275255 0012352 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
defined( 'ABSPATH' ) || exit;
/**
* Class ActionSchedulerJobMonitor
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class ActionSchedulerJobMonitor implements Service {
use PluginHelper;
/**
* @var ActionSchedulerInterface
*/
protected $action_scheduler;
/**
* @var bool[] Array of `true` values for each job that is monitored. A hash string generated by `self::get_job_hash`
* is used as keys.
*/
protected $monitored_hooks = [];
/**
* ActionSchedulerJobMonitor constructor.
*
* @param ActionSchedulerInterface $action_scheduler
*/
public function __construct( ActionSchedulerInterface $action_scheduler ) {
$this->action_scheduler = $action_scheduler;
}
/**
* Check whether the failure rate is above the specified threshold within the timeframe.
*
* To protect against failing jobs running forever the job's failure rate is checked before creating a new batch.
* By default, a job is stopped if it has 5 failures in the last hour.
*
* @param ActionSchedulerJobInterface $job
* @param string $hook The job action hook.
* @param array|null $args The job arguments.
*
* @throws JobException If the job's error rate is above the threshold.
*/
public function validate_failure_rate( ActionSchedulerJobInterface $job, string $hook, ?array $args = null ) {
if ( $this->is_failure_rate_above_threshold( $hook, $args ) ) {
throw JobException::stopped_due_to_high_failure_rate( $job->get_name() );
}
}
/**
* Reschedules the job if it has failed due to timeout.
*
* @param string $hook The job action hook.
* @param array|null $args The job arguments.
*
* @since 1.7.0
*/
public function attach_timeout_monitor( string $hook, ?array $args = null ) {
$this->monitored_hooks[ self::get_job_hash( $hook, $args ) ] = true;
add_action(
'action_scheduler_unexpected_shutdown',
[ $this, 'reschedule_if_timeout' ],
10,
2
);
}
/**
* Detaches the timeout monitor that handles rescheduling jobs on timeout.
*
* @param string $hook The job action hook.
* @param array|null $args The job arguments.
*
* @since 1.7.0
*/
public function detach_timeout_monitor( string $hook, ?array $args = null ) {
unset( $this->monitored_hooks[ self::get_job_hash( $hook, $args ) ] );
remove_action( 'action_scheduler_unexpected_shutdown', [ $this, 'reschedule_if_timeout' ] );
}
/**
* Reschedules an action if it has failed due to a timeout error.
*
* The number of previous failures will be checked before rescheduling the action, and it must be below the
* specified threshold in `self::get_failure_rate_threshold` within the timeframe specified in
* `self::get_failure_timeframe` for the action to be rescheduled.
*
* @param int $action_id
* @param array $error
*
* @since 1.7.0
*/
public function reschedule_if_timeout( $action_id, $error ) {
if ( ! empty( $error ) && $this->is_timeout_error( $error ) ) {
$action = $this->action_scheduler->fetch_action( $action_id );
$hook = $action->get_hook();
$args = $action->get_args();
// Confirm that the job is initiated by GLA and monitored by this instance.
// The `self::attach_timeout_monitor` method will register the job's hook and arguments hash into the $monitored_hooks variable.
if ( $this->get_slug() !== $action->get_group() || ! $this->is_monitored_for_timeout( $hook, $args ) ) {
return;
}
// Check if the job has not failed more than the allowed threshold.
if ( $this->is_failure_rate_above_threshold( $hook, $args ) ) {
do_action(
'woocommerce_gla_debug_message',
sprintf( 'The %s job failed too many times, not rescheduling.', $hook ),
__METHOD__
);
return;
}
do_action(
'woocommerce_gla_debug_message',
sprintf( 'The %s job has failed due to a timeout error, rescheduling...', $hook ),
__METHOD__
);
$this->action_scheduler->schedule_immediate( $hook, $args );
}
}
/**
* 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
*
* @link https://www.php.net/manual/en/function.error-get-last.php
*
* @since 1.7.0
*/
protected function is_timeout_error( array $error ): bool {
return isset( $error['type'] ) && $error['type'] === E_ERROR &&
isset( $error['message'] ) && strpos( $error ['message'], 'Maximum execution time' ) !== false;
}
/**
* Check whether the job's failure rate is above the specified threshold within the timeframe.
*
* @param string $hook The job action hook.
* @param array|null $args The job arguments.
*
* @return bool True if the job's error rate is above the threshold, and false otherwise.
*
* @see ActionSchedulerJobMonitor::get_failure_rate_threshold()
* @see ActionSchedulerJobMonitor::get_failure_timeframe()
*
* @since 1.7.0
*/
protected function is_failure_rate_above_threshold( string $hook, ?array $args = null ): bool {
$failed_actions = $this->action_scheduler->search(
[
'hook' => $hook,
'args' => $args,
'status' => $this->action_scheduler::STATUS_FAILED,
'per_page' => $this->get_failure_rate_threshold(),
'date' => gmdate( 'U' ) - $this->get_failure_timeframe(),
'date_compare' => '>',
],
'ids'
);
return count( $failed_actions ) >= $this->get_failure_rate_threshold();
}
/**
* Get the job failure rate threshold (per timeframe).
*
* @return int
*/
protected function get_failure_rate_threshold(): int {
return absint( apply_filters( 'woocommerce_gla_job_failure_rate_threshold', 3 ) );
}
/**
* Get the job failure timeframe (in seconds).
*
* @return int
*/
protected function get_failure_timeframe(): int {
return absint( apply_filters( 'woocommerce_gla_job_failure_timeframe', 2 * HOUR_IN_SECONDS ) );
}
/**
* Generates a unique hash (checksum) for each job using its hook name and arguments.
*
* @param string $hook
* @param array|null $args
*
* @return string
*
* @since 1.7.0
*/
protected static function get_job_hash( string $hook, ?array $args = null ): string {
return hash( 'crc32b', $hook . wp_json_encode( $args ) );
}
/**
* Determines whether the given set of job hook and arguments is monitored for timeout.
*
* @param string $hook
* @param array|null $args
*
* @return bool
*
* @since 1.7.0
*/
protected function is_monitored_for_timeout( string $hook, ?array $args = null ): bool {
return isset( $this->monitored_hooks[ self::get_job_hash( $hook, $args ) ] );
}
}
BatchedActionSchedulerJobInterface.php 0000644 00000001660 15154275255 0014076 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Interface BatchedActionSchedulerJobInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
interface BatchedActionSchedulerJobInterface extends ActionSchedulerJobInterface {
/**
* Handles batch creation action hook.
*
* @hooked gla/jobs/{$job_name}/create_batch
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @throws Exception If an error occurs.
*/
public function handle_create_batch_action( int $batch_number );
/**
* Handles processing a single batch action hook.
*
* @hooked gla/jobs/{$job_name}/process_item
*
* @param array $items The job items from the current batch.
*
* @throws Exception If an error occurs.
*/
public function handle_process_items_action( array $items );
}
CleanupProductsJob.php 0000644 00000003133 15154275255 0011036 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class CleanupProductsJob
*
* Deletes all stale Google products from Google Merchant Center.
* Stale products are the ones that are no longer relevant due to a change in merchant settings.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class CleanupProductsJob extends AbstractProductSyncerBatchedJob {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'cleanup_products_job';
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*/
public function get_batch( int $batch_number ): array {
return $this->product_repository->find_synced_product_ids( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function process_items( array $items ) {
$products = $this->product_repository->find_by_ids( $items );
$stale_entries = $this->batch_product_helper->generate_stale_products_request_entries( $products );
$this->product_syncer->delete_by_batch_requests( $stale_entries );
}
}
CleanupSyncedProducts.php 0000644 00000004254 15154275255 0011556 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
defined( 'ABSPATH' ) || exit;
/**
* Class CleanupSyncedProducts
*
* Marks products as unsynced when we disconnect the Merchant Account.
* The Merchant Center must remain disconnected during the job. If it is
* reconnected during the job it will stop processing, since the
* ProductSyncer will take over and update all the products.
*
* @since 1.12.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class CleanupSyncedProducts extends AbstractProductSyncerBatchedJob {
/**
* Get whether Merchant Center is connected.
*
* @return bool
*/
public function is_mc_connected(): bool {
return $this->merchant_center->is_connected();
}
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'cleanup_synced_products';
}
/**
* Can the job be scheduled.
* Only cleanup when the Merchant Center is disconnected.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! $this->is_running( $args ) && ! $this->is_mc_connected();
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return int[]
*/
public function get_batch( int $batch_number ): array {
return $this->product_repository->find_synced_product_ids( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Process batch items.
* Skips processing if the Merchant Center has been connected again.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*/
protected function process_items( array $items ) {
if ( $this->is_mc_connected() ) {
do_action(
'woocommerce_gla_debug_message',
sprintf(
'Skipping cleanup of unsynced products because Merchant Center is connected: %s',
implode( ',', $items )
),
__METHOD__
);
return;
}
$this->batch_product_helper->mark_batch_as_unsynced( $items );
}
}
DeleteAllProducts.php 0000644 00000003564 15154275255 0010657 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class DeleteAllProducts
*
* Deletes all WooCommerce products from Google Merchant Center.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class DeleteAllProducts extends AbstractProductSyncerBatchedJob {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'delete_all_products';
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return int[]
*/
protected function get_batch( int $batch_number ): array {
return $this->product_repository->find_synced_product_ids( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function process_items( array $items ) {
$products = $this->product_repository->find_by_ids( $items );
$product_entries = $this->batch_product_helper->generate_delete_request_entries( $products );
$this->product_syncer->delete_by_batch_requests( $product_entries );
}
/**
* Called when the job is completed.
*
* @since 2.6.4
*
* @param int $final_batch_number The final batch number when the job was completed.
* If equal to 1 then no items were processed by the job.
*/
protected function handle_complete( int $final_batch_number ) {
$this->merchant_statuses->maybe_refresh_status_data( true );
}
}
DeleteCoupon.php 0000644 00000005125 15154275255 0007661 0 ustar 00 <?php
declare(strict_types = 1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponSyncerException;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\DeleteCouponEntry;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Google\Service\ShoppingContent\Promotion as GooglePromotion;
defined( 'ABSPATH' ) || exit();
/**
* Class DeleteCoupon
*
* Delete existing WooCommerce coupon from Google Merchant Center.
*
* Note: The job will not start if it is already running.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class DeleteCoupon extends AbstractCouponSyncerJob implements
StartOnHookInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'delete_coupon';
}
/**
* Process an item.
*
* @param array[] $coupon_entries
*
* @throws JobException If no valid coupon data is provided as argument. The exception will be logged by ActionScheduler.
* @throws CouponSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
public function process_items( array $coupon_entries ) {
$wc_coupon_id = $coupon_entries[0] ?? null;
$google_promotion = $coupon_entries[1] ?? null;
$google_ids = $coupon_entries[2] ?? null;
if ( ( ! is_int( $wc_coupon_id ) ) || empty( $google_promotion ) || empty( $google_ids ) ) {
throw JobException::item_not_provided(
'Required data for the coupon to delete'
);
}
$this->coupon_syncer->delete(
new DeleteCouponEntry(
$wc_coupon_id,
new GooglePromotion( $google_promotion ),
$google_ids
)
);
}
/**
* Schedule the job.
*
* @param array[] $args
*
* @throws JobException If no coupon is provided as argument. The exception will be logged by ActionScheduler.
*/
public function schedule( array $args = [] ) {
$coupon_entry = $args[0] ?? null;
if ( ! $coupon_entry instanceof DeleteCouponEntry ) {
throw JobException::item_not_provided(
'DeleteCouponEntry for the coupon to delete'
);
}
if ( $this->can_schedule( [ $coupon_entry ] ) ) {
$this->action_scheduler->schedule_immediate(
$this->get_process_item_hook(),
[
[
$coupon_entry->get_wc_coupon_id(),
$coupon_entry->get_google_promotion(),
$coupon_entry->get_synced_google_ids(),
],
]
);
}
}
/**
* Get the name of an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook {
return new StartHook( "{$this->get_hook_base_name()}start" );
}
}
DeleteProducts.php 0000644 00000004403 15154275255 0010217 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\BatchProductIDRequestEntry;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\ProductIDMap;
defined( 'ABSPATH' ) || exit;
/**
* Class DeleteProducts
*
* Deletes WooCommerce products from Google Merchant Center.
*
* Note: The job will not start if it is already running.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class DeleteProducts extends AbstractProductSyncerJob implements StartOnHookInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'delete_products';
}
/**
* Process an item.
*
* @param string[] $product_id_map An array of Google product IDs mapped to WooCommerce product IDs as their key.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
public function process_items( array $product_id_map ) {
$product_entries = BatchProductIDRequestEntry::create_from_id_map( new ProductIDMap( $product_id_map ) );
$this->product_syncer->delete_by_batch_requests( $product_entries );
}
/**
* Schedule the job.
*
* @param array $args
*
* @throws JobException If no product is provided as argument. The exception will be logged by ActionScheduler.
*/
public function schedule( array $args = [] ) {
$args = $args[0] ?? [];
$id_map = ( new ProductIDMap( $args ) )->get();
if ( empty( $id_map ) ) {
throw JobException::item_not_provided( 'Array of WooCommerce product IDs' );
}
if ( did_action( 'woocommerce_gla_batch_retry_delete_products' ) ) {
// Retry after one minute.
$this->action_scheduler->schedule_single( gmdate( 'U' ) + 60, $this->get_process_item_hook(), [ $id_map ] );
} elseif ( $this->can_schedule( [ $id_map ] ) ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook(), [ $id_map ] );
}
}
/**
* Get an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook {
return new StartHook( 'woocommerce_gla_batch_retry_delete_products', 1 );
}
}
JobException.php 0000644 00000003636 15154275255 0007671 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\GoogleListingsAndAdsException;
use RuntimeException;
defined( 'ABSPATH' ) || exit;
/**
* Class JobException
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class JobException extends RuntimeException implements GoogleListingsAndAdsException {
/**
* Create a new exception instance for when a job item is not found.
*
* @return static
*/
public static function item_not_found(): JobException {
return new static( __( 'Job item not found.', 'google-listings-and-ads' ) );
}
/**
* Create a new exception instance for when a required job item is not provided.
*
* @param string $item The item name.
*
* @return static
*/
public static function item_not_provided( string $item ): JobException {
return new static(
sprintf(
/* translators: %s: the job item name */
__( 'Required job item "%s" not provided.', 'google-listings-and-ads' ),
$item
)
);
}
/**
* Create a new exception instance for when a job is stopped due to a high failure rate.
*
* @param string $job_name
*
* @return static
*/
public static function stopped_due_to_high_failure_rate( string $job_name ): JobException {
return new static(
sprintf(
/* translators: %s: the job name */
__( 'The "%s" job was stopped because its failure rate is above the allowed threshold.', 'google-listings-and-ads' ),
$job_name
)
);
}
/**
* Create a new exception instance for when a job class is not found.
*
* @param string $job_classname
*
* @return static
*/
public static function job_does_not_exist( string $job_classname ): JobException {
return new static(
sprintf(
/* translators: %s: the job classname */
__( 'The job "%s" does not exist.', 'google-listings-and-ads' ),
$job_classname
)
);
}
}
JobInitializer.php 0000644 00000004360 15154275255 0010211 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Conditional;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use DateTime;
defined( 'ABSPATH' ) || exit;
/**
* Class JobInitializer
*
* Initializes all jobs when certain conditions are met (e.g. the request is async or initiated by CRON, CLI, etc.).
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class JobInitializer implements Registerable, Conditional {
/**
* @var JobRepository
*/
protected $job_repository;
/**
* @var ActionSchedulerInterface
*/
protected $action_scheduler;
/**
* JobInitializer constructor.
*
* @param JobRepository $job_repository
* @param ActionSchedulerInterface $action_scheduler
*/
public function __construct( JobRepository $job_repository, ActionSchedulerInterface $action_scheduler ) {
$this->job_repository = $job_repository;
$this->action_scheduler = $action_scheduler;
}
/**
* Initialize all jobs.
*/
public function register(): void {
foreach ( $this->job_repository->list() as $job ) {
$job->init();
if ( $job instanceof StartOnHookInterface ) {
add_action(
$job->get_start_hook()->get_hook(),
function ( ...$args ) use ( $job ) {
$job->schedule( $args );
},
10,
$job->get_start_hook()->get_argument_count()
);
}
if (
$job instanceof RecurringJobInterface &&
! $this->action_scheduler->has_scheduled_action( $job->get_start_hook()->get_hook() ) &&
$job->can_schedule()
) {
$recurring_date_time = new DateTime( 'tomorrow 3am', wp_timezone() );
$schedule = '0 3 * * *'; // 3 am every day
$this->action_scheduler->schedule_cron( $recurring_date_time->getTimestamp(), $schedule, $job->get_start_hook()->get_hook() );
}
}
}
/**
* Check whether this object is currently needed.
*
* @return bool Whether the object is needed.
*/
public static function is_needed(): bool {
return ( defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) || ( defined( 'WP_CLI' ) && WP_CLI ) || is_admin() );
}
}
JobInterface.php 0000644 00000001251 15154275255 0007622 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\DependencyManagement\JobServiceProvider;
defined( 'ABSPATH' ) || exit;
/**
* Interface JobInterface
*
* Note: In order for the jobs to be initialized/registered, they need to be added to the container.
*
* @see JobServiceProvider to add job classes to the container.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
interface JobInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string;
/**
* Init the job.
*/
public function init(): void;
}
JobRepository.php 0000644 00000003020 15154275255 0010075 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\ContainerAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\ContainerAwareInterface;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class JobRepository
*
* ContainerAware used for:
* - JobInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class JobRepository implements ContainerAwareInterface, Service {
use ContainerAwareTrait;
/**
* @var JobInterface[] indexed by class name.
*/
protected $jobs = [];
/**
* Fetch all jobs from Container.
*
* @return JobInterface[]
*/
public function list(): array {
foreach ( $this->container->get( JobInterface::class ) as $job ) {
$this->jobs[ get_class( $job ) ] = $job;
}
return $this->jobs;
}
/**
* Fetch job from Container (or cache if previously fetched).
*
* @param string $classname Job class name.
*
* @return JobInterface
*
* @throws JobException If the job is not found.
*/
public function get( string $classname ): JobInterface {
if ( ! isset( $this->jobs[ $classname ] ) ) {
try {
$job = $this->container->get( $classname );
} catch ( Exception $e ) {
throw JobException::job_does_not_exist( $classname );
}
$classname = get_class( $job );
$this->jobs[ $classname ] = $job;
}
return $this->jobs[ $classname ];
}
}
MigrateGTIN.php 0000644 00000011402 15154275255 0007340 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\HelperTraits\GTINMigrationUtilities;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeManager;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class MigrateGTIN
*
* Schedules GTIN migration for all the products in the store.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
* @since 2.9.0
*/
class MigrateGTIN extends AbstractBatchedActionSchedulerJob implements OptionsAwareInterface {
use OptionsAwareTrait;
use GTINMigrationUtilities;
public const GTIN_MIGRATION_COMPLETED = 'completed';
public const GTIN_MIGRATION_STARTED = 'started';
public const GTIN_MIGRATION_READY = 'ready';
public const GTIN_MIGRATION_UNAVAILABLE = 'unavailable';
/**
* @var ProductRepository
*/
protected $product_repository;
/**
* @var AttributeManager
*/
protected $attribute_manager;
/**
* MigrateGTIN constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param ProductRepository $product_repository
* @param AttributeManager $attribute_manager
*/
public function __construct( ActionSchedulerInterface $action_scheduler, ActionSchedulerJobMonitor $monitor, ProductRepository $product_repository, AttributeManager $attribute_manager ) {
parent::__construct( $action_scheduler, $monitor );
$this->product_repository = $product_repository;
$this->attribute_manager = $attribute_manager;
}
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'migrate_gtin';
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return ! parent::is_running( $args ) && $this->is_gtin_available_in_core();
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*/
protected function process_items( array $items ) {
// update the product core GTIN using G4W GTIN
$products = $this->product_repository->find_by_ids( $items );
foreach ( $products as $product ) {
// process variations
if ( $product instanceof \WC_Product_Variable ) {
$variations = $product->get_children();
$this->process_items( $variations );
continue;
}
if ( $product->get_global_unique_id() ) {
$this->debug( $this->error_gtin_already_set( $product ) );
continue;
}
$gtin = $this->get_gtin( $product );
if ( ! $gtin ) {
$this->debug( $this->error_gtin_not_found( $product ) );
continue;
}
$gtin = $this->prepare_gtin( $gtin );
if ( ! is_numeric( $gtin ) ) {
$this->debug( $this->error_gtin_invalid( $product, $gtin ) );
continue;
}
try {
$product->set_global_unique_id( $gtin );
$product->save();
$this->debug( $this->successful_migrated_gtin( $product, $gtin ) );
} catch ( Exception $e ) {
$this->debug( $this->error_gtin_not_saved( $product, $gtin, $e ) );
}
}
}
/**
* Tweak schedule function for adding a start flag.
*
* @param array $args
*/
public function schedule( array $args = [] ) {
$this->options->update( OptionsInterface::GTIN_MIGRATION_STATUS, self::GTIN_MIGRATION_STARTED );
parent::schedule( $args );
}
/**
*
* To run when the job is completed.
*
* @param int $final_batch_number
*/
public function handle_complete( int $final_batch_number ) {
$this->options->update( OptionsInterface::GTIN_MIGRATION_STATUS, self::GTIN_MIGRATION_COMPLETED );
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*
* @throws Exception If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function get_batch( int $batch_number ): array {
return $this->product_repository->find_all_product_ids( $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Debug info in the logs.
*
* @param string $message
*
* @return void
*/
protected function debug( string $message ): void {
do_action(
'woocommerce_gla_debug_message',
$message,
__METHOD__
);
}
}
Notifications/AbstractItemNotificationJob.php 0000644 00000010505 15154275255 0015466 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\NotificationStatus;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractItemNotificationJob
* Generic class for the Notification Jobs containing items
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
abstract class AbstractItemNotificationJob extends AbstractNotificationJob {
/**
* Logic when processing the items
*
* @param array $args Arguments with the item id and the topic
*/
protected function process_items( array $args ): void {
if ( ! isset( $args['item_id'] ) || ! isset( $args['topic'] ) ) {
do_action(
'woocommerce_gla_error',
'Error sending the Notification. Topic and Item ID are mandatory',
__METHOD__
);
return;
}
$item = $args['item_id'];
$topic = $args['topic'];
$data = $args['data'] ?? [];
try {
if ( $this->can_process( $item, $topic ) && $this->notifications_service->notify( $topic, $item, $data ) ) {
$this->set_status( $item, $this->get_after_notification_status( $topic ) );
$this->handle_notified( $topic, $item );
}
} catch ( InvalidValue $e ) {
do_action(
'woocommerce_gla_error',
sprintf( 'Error sending Notification for - Item ID: %s - Topic: %s - Data %s. Product was deleted from the database before the notification was sent.', $item, $topic, wp_json_encode( $data ) ),
__METHOD__
);
}
}
/**
* Set the notification status for the item.
*
* @param int $item_id
* @param string $status
* @throws InvalidValue If the given ID doesn't reference a valid product.
*/
protected function set_status( int $item_id, string $status ): void {
$item = $this->get_item( $item_id );
$this->get_helper()->set_notification_status( $item, $status );
}
/**
* Get the Notification Status after the notification happens
*
* @param string $topic
* @return string
*/
protected function get_after_notification_status( string $topic ): string {
if ( $this->is_create_topic( $topic ) ) {
return NotificationStatus::NOTIFICATION_CREATED;
} elseif ( $this->is_delete_topic( $topic ) ) {
return NotificationStatus::NOTIFICATION_DELETED;
} else {
return NotificationStatus::NOTIFICATION_UPDATED;
}
}
/**
* Checks if the item can be processed based on the topic.
* This is needed because the item can change the Notification Status before
* the Job process the item.
*
* @param int $item_id
* @param string $topic
* @throws InvalidValue If the given ID doesn't reference a valid product.
* @return bool
*/
protected function can_process( int $item_id, string $topic ): bool {
$item = $this->get_item( $item_id );
if ( $this->is_create_topic( $topic ) ) {
return $this->get_helper()->should_trigger_create_notification( $item );
} elseif ( $this->is_delete_topic( $topic ) ) {
return $this->get_helper()->should_trigger_delete_notification( $item );
} else {
return $this->get_helper()->should_trigger_update_notification( $item );
}
}
/**
* Handle the item after the notification.
*
* @param string $topic
* @param int $item
* @throws InvalidValue If the given ID doesn't reference a valid product.
*/
protected function handle_notified( string $topic, int $item ): void {
if ( $this->is_delete_topic( $topic ) ) {
$this->get_helper()->mark_as_unsynced( $this->get_item( $item ) );
}
if ( $this->is_create_topic( $topic ) ) {
$this->get_helper()->mark_as_notified( $this->get_item( $item ) );
}
}
/**
* If a topic is a delete topic
*
* @param string $topic The topic to check
*
* @return bool
*/
protected function is_delete_topic( $topic ): bool {
return str_contains( $topic, '.delete' );
}
/**
* If a topic is a create topic
*
* @param string $topic The topic to check
*
* @return bool
*/
protected function is_create_topic( $topic ): bool {
return str_contains( $topic, '.create' );
}
/**
* Get the item
*
* @param int $item_id
* @return \WC_Product|\WC_Coupon
*/
abstract protected function get_item( int $item_id );
/**
* Get the helper
*
* @return HelperNotificationInterface
*/
abstract public function get_helper(): HelperNotificationInterface;
}
Notifications/AbstractNotificationJob.php 0000644 00000005372 15154275256 0014656 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\AbstractActionSchedulerJob;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ActionSchedulerJobMonitor;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobInterface;
defined( 'ABSPATH' ) || exit;
/**
* Class AbstractNotificationJob
* Generic class for the Notifications Jobs
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
abstract class AbstractNotificationJob extends AbstractActionSchedulerJob implements JobInterface {
/**
* @var NotificationsService $notifications_service
*/
protected $notifications_service;
/**
* Notifications Jobs constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param NotificationsService $notifications_service
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
NotificationsService $notifications_service
) {
$this->notifications_service = $notifications_service;
parent::__construct( $action_scheduler, $monitor );
}
/**
* Get the parent job name
*
* @return string
*/
public function get_name(): string {
return 'notifications/' . $this->get_job_name();
}
/**
* Schedule the Job
*
* @param array $args
*/
public function schedule( array $args = [] ): void {
if ( $this->can_schedule( [ $args ] ) ) {
$this->action_scheduler->schedule_immediate(
$this->get_process_item_hook(),
[ $args ]
);
}
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
/**
* Allow users to disable the notification job schedule.
*
* @since 2.8.0
*
* @param bool $value The current filter value. By default, it is the result of `$this->can_schedule` function.
* @param string $job_name The current Job name.
* @param array $args The arguments for the schedule function with the item id and the topic.
*/
return apply_filters( 'woocommerce_gla_notification_job_can_schedule', $this->notifications_service->is_ready() && parent::can_schedule( $args ), $this->get_job_name(), $args );
}
/**
* Get the child job name
*
* @return string
*/
abstract public function get_job_name(): string;
/**
* Logic when processing the items
*
* @param array $args
*/
abstract protected function process_items( array $args ): void;
}
Notifications/CouponNotificationJob.php 0000644 00000003606 15154275256 0014354 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ActionSchedulerJobMonitor;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications\HelperNotificationInterface;
defined( 'ABSPATH' ) || exit;
/**
* Class CouponNotificationJob
* Class for the Coupons Notifications Jobs
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
class CouponNotificationJob extends AbstractItemNotificationJob {
/**
* @var CouponHelper $helper
*/
protected $helper;
/**
* Notifications Jobs constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param NotificationsService $notifications_service
* @param HelperNotificationInterface $coupon_helper
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
NotificationsService $notifications_service,
HelperNotificationInterface $coupon_helper
) {
$this->helper = $coupon_helper;
parent::__construct( $action_scheduler, $monitor, $notifications_service );
}
/**
* Get the coupon
*
* @param int $item_id
* @return \WC_Coupon
*/
protected function get_item( int $item_id ) {
return $this->helper->get_wc_coupon( $item_id );
}
/**
* Get the Coupon Helper
*
* @return HelperNotificationInterface
*/
public function get_helper(): HelperNotificationInterface {
return $this->helper;
}
/**
* Get the job name
*
* @return string
*/
public function get_job_name(): string {
return 'coupons';
}
}
Notifications/HelperNotificationInterface.php 0000644 00000002714 15154275256 0015515 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
defined( 'ABSPATH' ) || exit;
/**
* Interface HelperNotificationInterface
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
interface HelperNotificationInterface {
/**
* Checks if the item can be processed based on the topic.
*
* @param WC_Product|WC_Coupon $item
*
* @return bool
*/
public function should_trigger_create_notification( $item ): bool;
/**
* Indicates if the item ready for sending a delete Notification.
*
* @param WC_Product|WC_Coupon $item
*
* @return bool
*/
public function should_trigger_delete_notification( $item ): bool;
/**
* Indicates if the item ready for sending an update Notification.
*
* @param WC_Product|WC_Coupon $item
*
* @return bool
*/
public function should_trigger_update_notification( $item ): bool;
/**
* Marks the item as unsynced.
*
* @param WC_Product|WC_Coupon $item
*
* @return void
*/
public function mark_as_unsynced( $item ): void;
/**
* Set the notification status for an item.
*
* @param WC_Product|WC_Coupon $item
* @param string $status
*
* @return void
*/
public function set_notification_status( $item, $status ): void;
/**
* Marks the item as notified.
*
* @param WC_Product|WC_Coupon $item
*
* @return void
*/
public function mark_as_notified( $item ): void;
}
Notifications/ProductNotificationJob.php 0000644 00000005143 15154275256 0014527 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\WP\NotificationsService;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ActionSchedulerJobMonitor;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductNotificationJob
* Class for the Product Notifications Jobs
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
class ProductNotificationJob extends AbstractItemNotificationJob {
use PluginHelper;
/**
* @var ProductHelper $helper
*/
protected $helper;
/**
* Notifications Jobs constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param NotificationsService $notifications_service
* @param HelperNotificationInterface $helper
*/
public function __construct(
ActionSchedulerInterface $action_scheduler,
ActionSchedulerJobMonitor $monitor,
NotificationsService $notifications_service,
HelperNotificationInterface $helper
) {
$this->helper = $helper;
parent::__construct( $action_scheduler, $monitor, $notifications_service );
}
/**
* Override Product Notification adding Offer ID for deletions.
* The offer_id might match the real offer ID or not, depending on whether the product has been synced by us or not.
* Google should check on their side if the product actually exists.
*
* @param array $args Arguments with the item id and the topic.
*/
protected function process_items( $args ): void {
if ( isset( $args['topic'] ) && isset( $args['item_id'] ) && $this->is_delete_topic( $args['topic'] ) ) {
$args['data'] = [ 'offer_id' => $this->helper->get_offer_id( $args['item_id'] ) ];
}
parent::process_items( $args );
}
/**
* Get the product
*
* @param int $item_id
* @throws InvalidValue If the given ID doesn't reference a valid product.
*
* @return \WC_Product
*/
protected function get_item( int $item_id ) {
return $this->helper->get_wc_product( $item_id );
}
/**
* Get the Product Helper
*
* @return ProductHelper
*/
public function get_helper(): HelperNotificationInterface {
return $this->helper;
}
/**
* Get the job name
*
* @return string
*/
public function get_job_name(): string {
return 'products';
}
}
Notifications/SettingsNotificationJob.php 0000644 00000001372 15154275256 0014707 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
defined( 'ABSPATH' ) || exit;
/**
* Class SettingsNotificationJob
* Class for the Settings Notifications
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
class SettingsNotificationJob extends AbstractNotificationJob {
/**
* Logic when processing the items
*
* @param array $args Arguments for the notification
*/
protected function process_items( array $args ): void {
$this->notifications_service->notify( $this->notifications_service::TOPIC_SETTINGS_UPDATED );
}
/**
* Get the job name
*
* @return string
*/
public function get_job_name(): string {
return 'settings';
}
}
Notifications/ShippingNotificationJob.php 0000644 00000001407 15154275256 0014667 0 ustar 00 <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications;
defined( 'ABSPATH' ) || exit;
/**
* Class ShippingNotificationJob
* Class for the Shipping Notifications
*
* @since 2.8.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Notifications
*/
class ShippingNotificationJob extends AbstractNotificationJob {
/**
* Get the job name
*
* @return string
*/
public function get_job_name(): string {
return 'shipping';
}
/**
* Logic when processing the items
*
* @param array $args Arguments for the notification
*/
protected function process_items( array $args ): void {
$this->notifications_service->notify( $this->notifications_service::TOPIC_SHIPPING_UPDATED, null, $args );
}
}
ProductSyncStats.php 0000644 00000003117 15154275256 0010567 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionScheduler;
defined( 'ABSPATH' ) || exit;
/**
* Class ProductSyncStats
*
* Counts how many scheduled jobs we have for syncing products.
* A scheduled job can either be a batch or an individual product.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class ProductSyncStats {
/**
* The ActionScheduler object.
*
* @var ActionScheduler
*/
protected $scheduler;
/**
* Job names for syncing products.
*/
protected const MATCHES = [
'refresh_synced_products',
'update_all_products',
'update_products',
'delete_products',
];
/**
* ProductSyncStats constructor.
*
* @param ActionScheduler $scheduler
*/
public function __construct( ActionScheduler $scheduler ) {
$this->scheduler = $scheduler;
}
/**
* Check if a job name is used for product syncing.
*
* @param string $hook
*
* @return bool
*/
protected function job_matches( string $hook ): bool {
foreach ( self::MATCHES as $match ) {
if ( false !== stripos( $hook, $match ) ) {
return true;
}
}
return false;
}
/**
* Return the amount of product sync jobs which are pending.
*
* @return int
*/
public function get_count(): int {
$count = 0;
$scheduled = $this->scheduler->search(
[
'status' => 'pending',
'per_page' => -1,
]
);
foreach ( $scheduled as $action ) {
if ( $this->job_matches( $action->get_hook() ) ) {
++$count;
}
}
return $count;
}
}
RecurringJobInterface.php 0000644 00000000643 15154275256 0011510 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
defined( 'ABSPATH' ) || exit;
/**
* Interface RecurringJobInterface
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
interface RecurringJobInterface extends StartOnHookInterface {
/**
* Return the recurring job's interval in seconds.
*
* @return int
*/
public function get_interval(): int;
}
ResubmitExpiringProducts.php 0000644 00000003500 15154275256 0012313 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class ResubmitExpiringProducts
*
* Resubmits all WooCommerce products that are nearly expired to Google Merchant Center.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class ResubmitExpiringProducts extends AbstractProductSyncerBatchedJob implements RecurringJobInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'resubmit_expiring_products';
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*/
public function get_batch( int $batch_number ): array {
return $this->product_repository->find_expiring_product_ids( $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function process_items( array $items ) {
$products = $this->product_repository->find_by_ids( $items );
$this->product_syncer->update( $products );
}
/**
* Return the recurring job's interval in seconds.
*
* @return int
*/
public function get_interval(): int {
return 24 * 60 * 60; // 24 hours
}
/**
* Get the name of an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook {
return new StartHook( "{$this->get_hook_base_name()}start" );
}
}
StartHook.php 0000644 00000001630 15154275256 0007207 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
defined( 'ABSPATH' ) || exit;
/**
* Class StartHook
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class StartHook {
/**
* @var string
*/
protected $hook;
/**
* @var int
*/
protected $argument_count;
/**
* StartHook constructor.
*
* @param string $hook The name of an action hook to attach the job's start method to
* @param int $argument_count The number of arguments returned by the specified action hook
*/
public function __construct( string $hook, int $argument_count = 0 ) {
$this->hook = $hook;
$this->argument_count = $argument_count;
}
/**
* @return string
*/
public function get_hook(): string {
return $this->hook;
}
/**
* @return int
*/
public function get_argument_count(): int {
return $this->argument_count;
}
}
StartOnHookInterface.php 0000644 00000001036 15154275256 0011325 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
defined( 'ABSPATH' ) || exit;
/**
* Interface StartOnHookInterface
*
* Action Scheduler jobs that implement this interface will start on a specific action hook.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
interface StartOnHookInterface extends ActionSchedulerJobInterface {
/**
* Get an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook;
}
SyncableProductsBatchedActionSchedulerJobTrait.php 0000644 00000005323 15154275256 0016467 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\FilteredProductList;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\JobException;
use Exception;
use WC_Product;
/*
* Contains AbstractBatchedActionSchedulerJob methods.
*
* @since 2.2.0
*/
trait SyncableProductsBatchedActionSchedulerJobTrait {
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return WC_Product[]
*/
public function get_batch( int $batch_number ): array {
return $this->get_filtered_batch( $batch_number )->get();
}
/**
* Get a single filtered batch of items.
*
* If no items are returned the job will stop.
*
* @since 1.4.0
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return FilteredProductList
*/
protected function get_filtered_batch( int $batch_number ): FilteredProductList {
return $this->product_repository->find_sync_ready_products( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Handles batch creation action hook.
*
* @hooked gla/jobs/{$job_name}/create_batch
*
* Schedules an action to run immediately for the items in the batch.
* Uses the unfiltered count to check if there are additional batches.
*
* @since 1.4.0
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @throws Exception If an error occurs.
* @throws JobException If the job failure rate is too high.
*/
public function handle_create_batch_action( int $batch_number ) {
$create_batch_hook = $this->get_create_batch_hook();
$create_batch_args = [ $batch_number ];
$this->monitor->validate_failure_rate( $this, $create_batch_hook, $create_batch_args );
if ( $this->retry_on_timeout ) {
$this->monitor->attach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
$items = $this->get_filtered_batch( $batch_number );
if ( 0 === $items->get_unfiltered_count() ) {
// if no more items the job is complete
$this->handle_complete( $batch_number );
} else {
// if items, schedule the process action
if ( count( $items ) ) {
$this->schedule_process_action( $items->get_product_ids() );
}
// Add another "create_batch" action to handle unfiltered items.
// The last batch created here will be an empty batch, it
// will call "handle_complete" to finish the job.
$this->schedule_create_batch_action( $batch_number + 1 );
}
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
}
Update/CleanupProductTargetCountriesJob.php 0000644 00000003314 15154275256 0015142 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\AbstractProductSyncerBatchedJob;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class CleanupProductTargetCountriesJob
*
* Deletes the previous list of target countries which was in use before the
* Global Offers option became available.
*
* @since 1.1.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update
*/
class CleanupProductTargetCountriesJob extends AbstractProductSyncerBatchedJob {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'cleanup_product_target_countries';
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*/
public function get_batch( int $batch_number ): array {
return $this->product_repository->find_synced_product_ids( [], $this->get_batch_size(), $this->get_query_offset( $batch_number ) );
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function process_items( array $items ) {
$products = $this->product_repository->find_by_ids( $items );
$stale_entries = $this->batch_product_helper->generate_stale_countries_request_entries( $products );
$this->product_syncer->delete_by_batch_requests( $stale_entries );
}
}
Update/PluginUpdate.php 0000644 00000003666 15154275256 0011127 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\Interfaces\InstallableInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobException;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\JobRepository;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\UpdateAllProducts;
defined( 'ABSPATH' ) || exit;
/**
* Runs update jobs when the plugin is updated.
*
* @since 1.1.0
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs\Update
*/
class PluginUpdate implements Service, InstallableInterface {
/**
* @var JobRepository
*/
protected $job_repository;
/**
* PluginUpdate constructor.
*
* @param JobRepository $job_repository
*/
public function __construct( JobRepository $job_repository ) {
$this->job_repository = $job_repository;
}
/**
* Update Jobs that need to be run per version.
*
* @var array
*/
private $updates = [
'1.0.1' => [
CleanupProductTargetCountriesJob::class,
UpdateAllProducts::class,
],
'1.12.6' => [
UpdateAllProducts::class,
],
];
/**
* Run installation logic for this class.
*
* @param string $old_version Previous version before updating.
* @param string $new_version Current version after updating.
*/
public function install( string $old_version, string $new_version ): void {
foreach ( $this->updates as $version => $jobs ) {
if ( version_compare( $old_version, $version, '<' ) ) {
$this->schedule_jobs( $jobs );
}
}
}
/**
* Schedules a list of jobs.
*
* @param array $jobs List of jobs
*/
protected function schedule_jobs( array $jobs ): void {
foreach ( $jobs as $job ) {
try {
$this->job_repository->get( $job )->schedule();
} catch ( JobException $e ) {
do_action( 'woocommerce_gla_exception', $e, __METHOD__ );
}
}
}
}
UpdateAllProducts.php 0000644 00000004106 15154275256 0010671 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class UpdateAllProducts
*
* Submits all WooCommerce products to Google Merchant Center and/or updates the existing ones.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class UpdateAllProducts extends AbstractProductSyncerBatchedJob implements OptionsAwareInterface {
use OptionsAwareTrait;
use SyncableProductsBatchedActionSchedulerJobTrait;
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_all_products';
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
protected function process_items( array $items ) {
$products = $this->product_repository->find_by_ids( $items );
$this->product_syncer->update( $products );
}
/**
* Schedules a delayed batched job
*
* @param int $delay The delay time in seconds
*/
public function schedule_delayed( int $delay ) {
if ( $this->can_schedule( [ 1 ] ) ) {
$this->action_scheduler->schedule_single( gmdate( 'U' ) + $delay, $this->get_create_batch_hook(), [ 1 ] );
}
}
/**
* Called when the job is completed.
*
* @param int $final_batch_number The final batch number when the job was completed.
* If equal to 1 then no items were processed by the job.
*/
protected function handle_complete( int $final_batch_number ) {
$this->options->update( OptionsInterface::UPDATE_ALL_PRODUCTS_LAST_SYNC, strtotime( 'now' ) );
$this->merchant_statuses->maybe_refresh_status_data( true );
}
}
UpdateCoupon.php 0000644 00000003657 15154275256 0007712 0 ustar 00 <?php
declare(strict_types = 1);
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Coupon\CouponSyncerException;
use WC_Coupon;
defined( 'ABSPATH' ) || exit();
/**
* Class UpdateCoupon
*
* Submits WooCommerce coupon to Google Merchant Center and/or updates the existing one.
*
* Note: The job will not start if it is already running.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class UpdateCoupon extends AbstractCouponSyncerJob implements
StartOnHookInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_coupon';
}
/**
* Process an item.
*
* @param int[] $coupon_ids
*
* @throws CouponSyncerException If an error occurs. The exception will be logged by ActionScheduler.
*/
public function process_items( $coupon_ids ) {
foreach ( $coupon_ids as $coupon_id ) {
$coupon = $this->wc->maybe_get_coupon( $coupon_id );
if ( $coupon instanceof WC_Coupon &&
$this->coupon_helper->is_sync_ready( $coupon ) ) {
$this->coupon_syncer->update( $coupon );
}
}
}
/**
* Schedule the job.
*
* @param array[] $args
*
* @throws JobException If no coupon is provided as argument. The exception will be logged by ActionScheduler.
*/
public function schedule( array $args = [] ) {
$args = $args[0] ?? null;
$coupon_ids = array_filter( $args, 'is_integer' );
if ( empty( $coupon_ids ) ) {
throw JobException::item_not_provided( 'WooCommerce Coupon IDs' );
}
if ( $this->can_schedule( [ $coupon_ids ] ) ) {
$this->action_scheduler->schedule_immediate(
$this->get_process_item_hook(),
[ $coupon_ids ]
);
}
}
/**
* Get the name of an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook {
return new StartHook( "{$this->get_hook_base_name()}start" );
}
}
UpdateMerchantProductStatuses.php 0000644 00000010422 15154275256 0013271 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\MerchantReport;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantStatuses;
use Throwable;
defined( 'ABSPATH' ) || exit;
/**
* Class UpdateMerchantProductStatuses
*
* Update Product Stats
*
* Note: The job will not start if it is already running or if the Google Merchant Center account is not connected.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*
* @since 2.6.4
*/
class UpdateMerchantProductStatuses extends AbstractActionSchedulerJob {
/**
* @var MerchantCenterService
*/
protected $merchant_center;
/**
* @var MerchantReport
*/
protected $merchant_report;
/**
* @var MerchantStatuses
*/
protected $merchant_statuses;
/**
* UpdateMerchantProductStatuses constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param MerchantCenterService $merchant_center
* @param MerchantReport $merchant_report
* @param MerchantStatuses $merchant_statuses
*/
public function __construct( ActionSchedulerInterface $action_scheduler, ActionSchedulerJobMonitor $monitor, MerchantCenterService $merchant_center, MerchantReport $merchant_report, MerchantStatuses $merchant_statuses ) {
parent::__construct( $action_scheduler, $monitor );
$this->merchant_center = $merchant_center;
$this->merchant_report = $merchant_report;
$this->merchant_statuses = $merchant_statuses;
}
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_merchant_product_statuses';
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return parent::can_schedule( $args ) && $this->merchant_center->is_connected();
}
/**
* Process the job.
*
* @param int[] $items An array of job arguments.
*
* @throws JobException If the merchant product statuses cannot be retrieved..
*/
public function process_items( array $items ) {
try {
$next_page_token = $items['next_page_token'] ?? null;
// Clear the cache if we're starting from the beginning.
if ( ! $next_page_token ) {
$this->merchant_statuses->clear_product_statuses_cache_and_issues();
$this->merchant_statuses->refresh_account_and_presync_issues();
}
$results = $this->merchant_report->get_product_view_report( $next_page_token );
$next_page_token = $results['next_page_token'];
$this->merchant_statuses->process_product_statuses( $results['statuses'] );
if ( $next_page_token ) {
$this->schedule( [ [ 'next_page_token' => $next_page_token ] ] );
} else {
$this->merchant_statuses->handle_complete_mc_statuses_fetching();
}
} catch ( Throwable $e ) {
$this->merchant_statuses->handle_failed_mc_statuses_fetching( $e->getMessage() );
throw new JobException( 'Error updating merchant product statuses: ' . $e->getMessage() );
}
}
/**
* Schedule the job.
*
* @param array $args - arguments.
*/
public function schedule( array $args = [] ) {
if ( $this->can_schedule( $args ) ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook(), $args );
}
}
/**
* The job is considered to be scheduled if the "process_item" action is currently pending or in-progress regardless of the arguments.
*
* @return bool
*/
public function is_scheduled(): bool {
// We set 'args' to null so it matches any arguments. This is because it's possible to have multiple instances of the job running with different page tokens
return $this->is_running( null );
}
/**
* Validate the failure rate of the job.
*
* @return string|void Returns an error message if the failure rate is too high, otherwise null.
*/
public function get_failure_rate_message() {
try {
$this->monitor->validate_failure_rate( $this, $this->get_process_item_hook() );
} catch ( JobException $e ) {
return $e->getMessage();
}
}
}
UpdateProducts.php 0000644 00000004372 15154275256 0010245 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncerException;
defined( 'ABSPATH' ) || exit;
/**
* Class UpdateProducts
*
* Submits WooCommerce products to Google Merchant Center and/or updates the existing ones.
*
* Note: The job will not start if it is already running.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
class UpdateProducts extends AbstractProductSyncerJob implements StartOnHookInterface {
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_products';
}
/**
* Process an item.
*
* @param int[] $product_ids An array of WooCommerce product ids.
*
* @throws ProductSyncerException If an error occurs. The exception will be logged by ActionScheduler.
* @throws JobException If invalid or non-existing products are provided. The exception will be logged by ActionScheduler.
*/
public function process_items( array $product_ids ) {
$args = [ 'include' => $product_ids ];
$products = $this->product_repository->find_sync_ready_products( $args )->get();
if ( empty( $products ) ) {
throw JobException::item_not_found();
}
$this->product_syncer->update( $products );
}
/**
* Schedule the job.
*
* @param array $args - arguments.
*
* @throws JobException If no product is provided as argument. The exception will be logged by ActionScheduler.
*/
public function schedule( array $args = [] ) {
$args = $args[0] ?? [];
$ids = array_filter( $args, 'is_integer' );
if ( empty( $ids ) ) {
throw JobException::item_not_provided( 'Array of WooCommerce Product IDs' );
}
if ( did_action( 'woocommerce_gla_batch_retry_update_products' ) ) {
$this->action_scheduler->schedule_single( gmdate( 'U' ) + 60, $this->get_process_item_hook(), [ $ids ] );
} elseif ( $this->can_schedule( [ $ids ] ) ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook(), [ $ids ] );
}
}
/**
* Get an action hook to attach the job's start method to.
*
* @return StartHook
*/
public function get_start_hook(): StartHook {
return new StartHook( 'woocommerce_gla_batch_retry_update_products', 1 );
}
}
UpdateShippingSettings.php 0000644 00000006041 15154275256 0011737 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Settings as GoogleSettings;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
defined( 'ABSPATH' ) || exit;
/**
* Class UpdateShippingSettings
*
* Submits WooCommerce shipping settings to Google Merchant Center replacing the existing shipping settings.
*
* Note: The job will not start if it is already running or if the Google Merchant Center account is not connected.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*
* @since 2.1.0
*/
class UpdateShippingSettings extends AbstractActionSchedulerJob {
/**
* @var MerchantCenterService
*/
protected $merchant_center;
/**
* @var GoogleSettings
*/
protected $google_settings;
/**
* UpdateShippingSettings constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param MerchantCenterService $merchant_center
* @param GoogleSettings $google_settings
*/
public function __construct( ActionSchedulerInterface $action_scheduler, ActionSchedulerJobMonitor $monitor, MerchantCenterService $merchant_center, GoogleSettings $google_settings ) {
parent::__construct( $action_scheduler, $monitor );
$this->merchant_center = $merchant_center;
$this->google_settings = $google_settings;
}
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_shipping_settings';
}
/**
* Can the job be scheduled.
*
* @param array|null $args
*
* @return bool Returns true if the job can be scheduled.
*/
public function can_schedule( $args = [] ): bool {
return parent::can_schedule( $args ) && $this->can_sync_shipping();
}
/**
* Process the job.
*
* @param int[] $items An array of job arguments.
*
* @throws JobException If the shipping settings cannot be synced.
*/
public function process_items( array $items ) {
if ( ! $this->can_sync_shipping() ) {
throw new JobException( 'Cannot sync shipping settings. Confirm that the merchant center account is connected and the option to automatically sync the shipping settings is selected.' );
}
$this->google_settings->sync_shipping();
}
/**
* Schedule the job.
*
* @param array $args - arguments.
*/
public function schedule( array $args = [] ) {
if ( $this->can_schedule() ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook() );
}
}
/**
* Can the WooCommerce shipping settings be synced to Google Merchant Center.
*
* @return bool
*/
protected function can_sync_shipping(): bool {
// Confirm that the Merchant Center account is connected and the user has chosen for the shipping rates to be synced from WooCommerce settings.
return $this->merchant_center->is_connected() && $this->google_settings->should_get_shipping_rates_from_woocommerce();
}
}
UpdateSyncableProductsCount.php 0000644 00000007145 15154275256 0012740 0 ustar 00 <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Automattic\WooCommerce\GoogleListingsAndAds\ActionScheduler\ActionSchedulerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\AbstractBatchedActionSchedulerJob;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\ActionSchedulerJobMonitor;
use Automattic\WooCommerce\GoogleListingsAndAds\Jobs\SyncableProductsBatchedActionSchedulerJobTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductRepository;
defined( 'ABSPATH' ) || exit;
/**
* Class UpdateSyncableProductsCount
*
* Get the number of syncable products (i.e. product ready to be synced to Google Merchant Center) and update it in the DB.
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
* @since 2.2.0
*/
class UpdateSyncableProductsCount extends AbstractBatchedActionSchedulerJob implements OptionsAwareInterface {
use OptionsAwareTrait;
use SyncableProductsBatchedActionSchedulerJobTrait;
/**
* @var ProductRepository
*/
protected $product_repository;
/**
* @var ProductHelper
*/
protected $product_helper;
/**
* UpdateSyncableProductsCount constructor.
*
* @param ActionSchedulerInterface $action_scheduler
* @param ActionSchedulerJobMonitor $monitor
* @param ProductRepository $product_repository
* @param ProductHelper $product_helper
*/
public function __construct( ActionSchedulerInterface $action_scheduler, ActionSchedulerJobMonitor $monitor, ProductRepository $product_repository, ProductHelper $product_helper ) {
parent::__construct( $action_scheduler, $monitor );
$this->product_repository = $product_repository;
$this->product_helper = $product_helper;
}
/**
* Get the name of the job.
*
* @return string
*/
public function get_name(): string {
return 'update_syncable_products_count';
}
/**
* Get job batch size.
*
* @return int
*/
protected function get_batch_size(): int {
/**
* Filters the batch size for the job.
*
* @param string Job's name
*/
return apply_filters( 'woocommerce_gla_batched_job_size', 500, $this->get_name() );
}
/**
* Process batch items.
*
* @param int[] $items A single batch of WooCommerce product IDs from the get_batch() method.
*/
protected function process_items( array $items ) {
$product_ids = $this->options->get( OptionsInterface::SYNCABLE_PRODUCTS_COUNT_INTERMEDIATE_DATA );
if ( ! is_array( $product_ids ) ) {
$product_ids = [];
}
$grouped_items = $this->product_helper->maybe_swap_for_parent_ids( $items );
$this->options->update( OptionsInterface::SYNCABLE_PRODUCTS_COUNT_INTERMEDIATE_DATA, array_unique( [ ...$product_ids, ...$grouped_items ] ) );
}
/**
* Called when the job is completed.
*
* @param int $final_batch_number The final batch number when the job was completed.
* If equal to 1 then no items were processed by the job.
*/
protected function handle_complete( int $final_batch_number ) {
$product_ids = $this->options->get( OptionsInterface::SYNCABLE_PRODUCTS_COUNT_INTERMEDIATE_DATA );
$count = is_array( $product_ids ) ? count( $product_ids ) : 0;
$this->options->update( OptionsInterface::SYNCABLE_PRODUCTS_COUNT, $count );
$this->options->delete( OptionsInterface::SYNCABLE_PRODUCTS_COUNT_INTERMEDIATE_DATA );
}
}