/
home
/
assocoweys
/
comgk
/
wp-content
/
plugins
/
google-site-kit
/
includes
/
Core
/
Email_Reporting
/
Upload File
HOME
<?php /** * Class Google\Site_Kit\Core\Email_Reporting\Worker_Task * * @package Google\Site_Kit\Core\Email_Reporting * @copyright 2025 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Email_Reporting; use Google\Site_Kit\Core\Permissions\Permissions; use WP_Post; /** * Handles worker cron callbacks for email reporting. * * @since 1.167.0 * @access private * @ignore */ class Worker_Task { /** * Email log batch query helper. * * @since 1.167.0 * * @var Email_Log_Batch_Query */ private $batch_query; /** * Scheduler instance. * * @since 1.167.0 * * @var Email_Reporting_Scheduler */ private $scheduler; /** * Email log processor. * * @since 1.170.0 * * @var Email_Log_Processor */ private $log_processor; /** * Email reporting data requests service. * * @since 1.172.0 * * @var Email_Reporting_Data_Requests */ private $data_requests; /** * Max execution limiter. * * @since 1.167.0 * * @var Max_Execution_Limiter */ private $max_execution_limiter; /** * Batch error notifier. * * @since 1.175.0 * * @var Batch_Error_Notifier */ private $notifier; /** * Cron health check service. * * @since 1.176.0 * * @var Cron_Health_Check */ private $health_check; /** * Constructor. * * @since 1.167.0 * @since 1.175.0 Added $notifier parameter. * * @param Max_Execution_Limiter $max_execution_limiter Execution limiter instance. * @param Email_Log_Batch_Query $batch_query Batch query helper. * @param Email_Reporting_Scheduler $scheduler Scheduler instance. * @param Email_Log_Processor $log_processor Log processor instance. * @param Email_Reporting_Data_Requests $data_requests Data requests helper. * @param Batch_Error_Notifier $notifier Batch error notifier. * @param Cron_Health_Check $health_check Cron health check service. */ public function __construct( Max_Execution_Limiter $max_execution_limiter, Email_Log_Batch_Query $batch_query, Email_Reporting_Scheduler $scheduler, Email_Log_Processor $log_processor, Email_Reporting_Data_Requests $data_requests, Batch_Error_Notifier $notifier, Cron_Health_Check $health_check ) { $this->max_execution_limiter = $max_execution_limiter; $this->batch_query = $batch_query; $this->scheduler = $scheduler; $this->log_processor = $log_processor; $this->data_requests = $data_requests; $this->notifier = $notifier; $this->health_check = $health_check; } /** * Handles worker cron executions for email reporting. * * @since 1.167.0 * * @param string $batch_id Batch identifier. * @param string $frequency Frequency slug. * @param int $initiator_timestamp Initiator timestamp. */ public function handle_callback_action( $batch_id, $frequency, $initiator_timestamp ) { $lock_handle = $this->acquire_lock( $frequency ); if ( ! $lock_handle ) { return; } $switched = false; try { if ( $this->should_abort( $initiator_timestamp ) ) { return; } if ( $this->batch_query->is_complete( $batch_id ) ) { return; } $pending_ids = $this->batch_query->get_pending_ids( $batch_id ); if ( empty( $pending_ids ) ) { return; } $site_id = (int) get_post_meta( $pending_ids[0], Email_Log::META_SITE_ID, true ); if ( 0 !== $site_id && get_current_blog_id() !== $site_id ) { // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog -- Needed to process the log in its site context. switch_to_blog( $site_id ); $switched = true; } $this->schedule_follow_up( $batch_id, $frequency, $initiator_timestamp ); if ( $this->should_abort( $initiator_timestamp ) ) { return; } $emails_processed = $this->process_pending_logs( $pending_ids, $frequency, $initiator_timestamp ); $this->health_check->track_worker_progress( $frequency, $emails_processed, $batch_id ); $this->notifier->maybe_notify( $batch_id ); } finally { if ( $switched ) { restore_current_blog(); } delete_transient( $lock_handle ); } } /** * Processes a list of pending email log IDs. * * @since 1.170.0 * * @param array $pending_ids Pending post IDs. * @param string $frequency Frequency slug. * @param int $initiator_timestamp Initiator timestamp. * @return int Number of emails processed and marked sent. */ private function process_pending_logs( array $pending_ids, $frequency, $initiator_timestamp ) { $shared_payloads = $this->get_shared_payloads_for_pending_ids( $pending_ids ); $processed_sent_ids = array(); foreach ( $pending_ids as $post_id ) { if ( $this->should_abort( $initiator_timestamp ) ) { return count( $processed_sent_ids ); } $previous_status = get_post_status( $post_id ); $email_log = get_post( $post_id ); $user = null; if ( $email_log instanceof WP_Post ) { $user = get_user_by( 'id', (int) $email_log->post_author ); } $shared_payloads_for_user = array(); if ( ! empty( $shared_payloads ) && $user instanceof \WP_User ) { foreach ( $shared_payloads as $slug => $module_payload ) { if ( user_can( $user, Permissions::MANAGE_OPTIONS ) || user_can( $user, Permissions::READ_SHARED_MODULE_DATA, $slug ) ) { $shared_payloads_for_user[ $slug ] = $module_payload; } } } if ( empty( $shared_payloads_for_user ) ) { $this->log_processor->process( $post_id, $frequency ); } else { $this->log_processor->process( $post_id, $frequency, $shared_payloads_for_user ); } if ( Email_Log::STATUS_SENT === get_post_status( $post_id ) && Email_Log::STATUS_SENT !== $previous_status ) { $processed_sent_ids[] = $post_id; } } return count( $processed_sent_ids ); } /** * Attempts to acquire a frequency-scoped worker lock. * * @since 1.167.0 * * @param string $frequency Frequency slug. * @return string|false Transient name on success, false if lock already held. */ private function acquire_lock( $frequency ) { $transient_name = sprintf( 'googlesitekit_email_reporting_worker_lock_%s', $frequency ); if ( get_transient( $transient_name ) ) { return false; } set_transient( $transient_name, time(), MINUTE_IN_SECONDS ); return $transient_name; } /** * Determines if the current worker run should abort. * * @since 1.167.0 * * @param int $initiator_timestamp Initiator timestamp. * @return bool True if processing should stop immediately. */ private function should_abort( $initiator_timestamp ) { return $this->max_execution_limiter->should_abort( $initiator_timestamp ); } /** * Schedules the follow-up worker event. * * @since 1.167.0 * * @param string $batch_id Batch identifier. * @param string $frequency Frequency slug. * @param int $initiator_timestamp Initiator timestamp. */ private function schedule_follow_up( $batch_id, $frequency, $initiator_timestamp ) { $target_time = time() + ( 11 * MINUTE_IN_SECONDS ); $delay = max( 0, $target_time - (int) $initiator_timestamp ); $this->scheduler->schedule_worker( $batch_id, $frequency, $initiator_timestamp, $delay ); } /** * Builds shared payloads per module for view-only recipients. * * @since 1.172.0 * * @param array $pending_ids Pending post IDs. * @return array Shared payloads keyed by module slug. */ private function get_shared_payloads_for_pending_ids( array $pending_ids ) { $module_slugs = $this->data_requests->get_active_module_slugs(); if ( empty( $module_slugs ) ) { return array(); } list( $date_range, $module_recipients ) = $this->collect_date_range_and_recipients( $pending_ids, $module_slugs ); if ( empty( $date_range ) ) { return array(); } return $this->build_shared_payloads( $module_recipients, $date_range ); } /** * Collects the date range and module recipients from pending logs. * * @since 1.172.0 * * @param array $pending_ids Pending post IDs. * @param string[] $module_slugs Active module slugs. * @return array{0: array, 1: array} Date range and module recipients. */ private function collect_date_range_and_recipients( array $pending_ids, array $module_slugs ) { $module_recipients = array(); $date_range = array(); foreach ( $pending_ids as $post_id ) { $email_log = get_post( $post_id ); if ( ! $email_log instanceof WP_Post || Email_Log::POST_TYPE !== $email_log->post_type ) { continue; } if ( empty( $date_range ) ) { $date_range = Email_Log::get_date_range_from_log( $email_log ); } $user_id = (int) $email_log->post_author; if ( $user_id <= 0 ) { continue; } $user = get_user_by( 'id', $user_id ); if ( ! $user instanceof \WP_User ) { continue; } foreach ( $module_slugs as $slug ) { // Ensure the module still has an owner at send time, since the // owner could have been removed after we initially collected // modules. if ( 0 === $this->data_requests->get_module_owner_id( $slug ) ) { // If there's no module owner, skip this module. continue; } // Only add this user to the receipt list if they have // permission to view the module data. if ( user_can( $user, Permissions::MANAGE_OPTIONS ) || user_can( $user, Permissions::READ_SHARED_MODULE_DATA, $slug ) ) { $module_recipients[ $slug ][ $user_id ] = true; } } } return array( $date_range, $module_recipients ); } /** * Builds shared module payloads based on a recipient map. * * @since 1.172.0 * * @param array $module_recipients Module recipients keyed by slug. * @param array $date_range Date range for the report. * @return array Shared payloads keyed by module slug. */ private function build_shared_payloads( array $module_recipients, array $date_range ) { $shared_payloads = array(); foreach ( $module_recipients as $slug => $user_ids ) { if ( empty( $user_ids ) ) { continue; } $shared_user_id = $this->data_requests->get_module_owner_id( $slug ); if ( $shared_user_id <= 0 ) { continue; } $payload = $this->data_requests->get_user_payload( $shared_user_id, $date_range, array(), array( $slug ) ); if ( is_wp_error( $payload ) || empty( $payload ) ) { continue; } if ( ! empty( $payload[ $slug ] ) ) { $shared_payloads[ $slug ] = $payload[ $slug ]; } } return $shared_payloads; } }