File "NotificationsProcessor.php"
Full Path: /home/capoeirajd/www/wp-content/plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
File size: 7.39 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\StockNotifications\AsyncTasks;
use Automattic\WooCommerce\Internal\StockNotifications\AsyncTasks\JobManager;
use Automattic\WooCommerce\Internal\StockNotifications\AsyncTasks\CycleStateService;
use Automattic\WooCommerce\Internal\StockNotifications\Utilities\EligibilityService;
use Automattic\WooCommerce\Internal\StockNotifications\Emails\EmailManager;
use Automattic\WooCommerce\Internal\StockNotifications\Factory;
use Automattic\WooCommerce\Internal\StockNotifications\Notification;
use Automattic\WooCommerce\Internal\StockNotifications\NotificationQuery;
use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationCancellationSource;
use WC_Product;
/**
* The async processor for sending stock notifications in bulk.
*/
class NotificationsProcessor {
/**
* The email manager.
*
* @var EmailManager
*/
private EmailManager $email_manager;
/**
* The logger.
*
* @var Logger
*/
private $logger;
/**
* The eligibility service.
*
* @var EligibilityService
*/
private EligibilityService $eligibility_service;
/**
* The job manager.
*
* @var JobManager
*/
private JobManager $job_manager;
/**
* The cycle state service.
*
* @var CycleStateService
*/
private CycleStateService $cycle_state_service;
/**
* The batch size for processing notifications.
*/
protected const BATCH_SIZE = 50;
/**
* Initialize the controller.
*
* @internal
*
* @param EligibilityService $eligibility_service The eligibility service.
* @param JobManager $job_manager The job manager.
* @param CycleStateService $cycle_state_service The cycle state service.
* @param EmailManager $email_manager The email manager.
* @return void
*/
final public function init(
EligibilityService $eligibility_service,
JobManager $job_manager,
CycleStateService $cycle_state_service,
EmailManager $email_manager
): void {
$this->eligibility_service = $eligibility_service;
$this->job_manager = $job_manager;
$this->cycle_state_service = $cycle_state_service;
$this->email_manager = $email_manager;
}
/**
* Constructor.
*/
public function __construct() {
$this->logger = \wc_get_logger();
add_action( JobManager::AS_JOB_SEND_STOCK_NOTIFICATIONS, array( $this, 'process_batch' ) );
}
/**
* Get the batch size for processing notifications.
*
* @return int
*/
private function get_batch_size(): int {
/**
* Filter: woocommerce_customer_stock_notifications_batch_size
*
* @since 10.2.0
*
* Allow customization of batch size for processing notifications.
*
* @param int $batch_size Default batch size.
* @return int
*/
return (int) apply_filters( 'woocommerce_customer_stock_notifications_batch_size', self::BATCH_SIZE );
}
/**
* Parse the product ID from the arguments.
*
* @param int $product_id The product ID.
* @return int
* @throws \Exception If the product is not found.
*/
private function parse_args( $product_id ): int {
if ( empty( $product_id ) || ! is_numeric( $product_id ) ) {
throw new \Exception( 'Invalid arguments.' );
}
$product_id = (int) $product_id;
if ( $product_id <= 0 ) {
throw new \Exception( 'Product ID is required.' );
}
return $product_id;
}
/**
* Parse the product.
*
* @param int $product_id The product ID.
* @return \WC_Product
* @throws \Exception If the product is not valid for notifications.
*/
private function parse_product( int $product_id ): WC_Product {
$product = wc_get_product( $product_id );
if ( ! $product instanceof WC_Product ) {
throw new \Exception( sprintf( 'Product %d not found.', absint( $product_id ) ) );
}
if ( ! $this->eligibility_service->is_product_eligible( $product ) ) {
throw new \Exception( sprintf( 'Product %d is not eligible for notifications.', $product->get_id() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
if ( ! $this->eligibility_service->is_stock_status_eligible( $product->get_stock_status() ) ) {
throw new \Exception( sprintf( 'Product %d stock status is not eligible for notifications (i.e. not in stock).', $product->get_id() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $product;
}
/**
* Process a batch of notifications.
*
* @param int $product_id The product ID from AS job args.
* @return void
*/
public function process_batch( $product_id ) {
// Sanity checks.
try {
$product_id = $this->parse_args( $product_id );
$cycle_state = $this->cycle_state_service->get_or_initialize_cycle_state( $product_id );
$product = $this->parse_product( $product_id );
} catch ( \Throwable $e ) {
$product_id = (int) $product_id ?? 0;
$this->logger->error(
sprintf( 'Background process for product %s terminated. Reason: %s', $product_id, $e->getMessage() ),
array(
'source' => 'wc-customer-stock-notifications',
'product_id' => $product_id,
'exception' => get_class( $e ),
)
);
// Clean up the cycle state.
if ( isset( $cycle_state ) ) {
$this->cycle_state_service->complete_cycle( $product_id, $cycle_state );
}
return;
}
$cycle_state['product_ids'] = $this->eligibility_service->get_target_product_ids( $product );
// Get notifications.
$notifications = NotificationQuery::get_notifications(
array(
'status' => NotificationStatus::ACTIVE,
'product_id' => $cycle_state['product_ids'],
'last_attempt_limit' => (int) $cycle_state['cycle_start_time'],
'return' => 'ids',
'limit' => $this->get_batch_size(),
'orderby' => 'id',
'order' => 'ASC',
)
);
if ( empty( $notifications ) ) {
$this->cycle_state_service->complete_cycle( $product_id, $cycle_state );
return;
}
foreach ( $notifications as $notification_id ) {
$notification = Factory::get_notification( $notification_id );
if ( ! $notification instanceof Notification ) {
$this->logger->error(
sprintf( 'Failed to get notification ID: %d', $notification_id ),
array( 'source' => 'wc-customer-stock-notifications' )
);
continue;
}
$notification->set_date_last_attempt( time() );
++$cycle_state['total_count'];
if ( $this->eligibility_service->should_skip_notification( $notification, $product ) ) {
++$cycle_state['skipped_count'];
$notification->save();
continue;
}
$is_sent = true;
try {
$this->email_manager->send_stock_notification_email( $notification );
} catch ( \Throwable $e ) {
$is_sent = false;
}
if ( $is_sent ) {
$notification->set_date_notified( time() );
$notification->set_status( NotificationStatus::SENT );
++$cycle_state['sent_count'];
} else {
$notification->set_status( NotificationStatus::CANCELLED );
$notification->set_cancellation_source( NotificationCancellationSource::SYSTEM );
++$cycle_state['failed_count'];
}
// Always save the notification to reflect last attempt time.
$notification->save();
}
if ( count( $notifications ) === $this->get_batch_size() ) {
$this->cycle_state_service->save_cycle_state( $product_id, $cycle_state );
$this->job_manager->schedule_next_batch_for_product( $product_id );
return;
}
$this->cycle_state_service->complete_cycle( $product_id, $cycle_state );
}
}