'product', 'post_status' => 'any', 'fields' => 'ids', 'posts_per_page' => 100, 'no_found_rows' => true, 'meta_key' => Meta::PENDING_SAVE, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- один раз при включении плагина. ) ); foreach ( $pending_products as $product_id ) { self::schedule_pending_save( (int) $product_id, self::retry_delay( 0 ) ); } delete_option( self::SETUP_OPTION ); Logger::info( 'Background jobs registered', array( 'pending_saves' => count( $pending_products ) ) ); } /** * Пауза перед следующей попыткой доделать сохранение. * * @param int $attempts Сколько попыток уже было. * @return int Секунды. */ public static function retry_delay( int $attempts ): int { return self::RETRY_DELAYS[ min( max( 0, $attempts ), count( self::RETRY_DELAYS ) - 1 ) ]; } /** * Ставит задачу «доделать сохранение» товара, заменяя прежнюю. * * @param int $product_id Id товара. * @param int $delay_seconds Через сколько секунд выполнить. * @return void */ public static function schedule_pending_save( int $product_id, int $delay_seconds ): void { if ( ! function_exists( 'as_schedule_single_action' ) ) { return; } self::cancel_pending_save( $product_id ); as_schedule_single_action( time() + $delay_seconds, self::SAVE_HOOK, array( $product_id ), self::GROUP ); } /** * Отменяет задачу «доделать сохранение» товара. * * @param int $product_id Id товара. * @return void */ public static function cancel_pending_save( int $product_id ): void { if ( function_exists( 'as_unschedule_all_actions' ) ) { as_unschedule_all_actions( self::SAVE_HOOK, array( $product_id ), self::GROUP ); } } /** * Задача: доделать ожидающее сохранение товара. * * Работает от имени того, кто нажал кнопку сохранения в окне, — вложения в медиатеке получают * его как автора, как при обычном сохранении. * * @param int|string $product_id Id товара — так Action Scheduler передаёт аргумент задачи. * @return void */ public function finish_pending_save( $product_id ): void { $product_id = (int) $product_id; $pending = Product_Saver::pending( $product_id ); if ( null === $pending ) { return; } $attempts = (int) ( $pending['attempts'] ?? 0 ) + 1; $pending['attempts'] = $attempts; update_post_meta( $product_id, Meta::PENDING_SAVE, $pending ); $user_id = (int) ( $pending['userId'] ?? 0 ); if ( $user_id > 0 && get_userdata( $user_id ) ) { wp_set_current_user( $user_id ); } $context = array( 'product_id' => $product_id, 'attempt' => $attempts, ); try { Product_Saver::process( $product_id ); Logger::info( 'Pending save finished in the background', $context ); } catch ( Action_Failed $failure ) { if ( $failure->temporary && $attempts < count( self::RETRY_DELAYS ) ) { self::schedule_pending_save( $product_id, self::retry_delay( $attempts ) ); Logger::warning( 'Pending save will be retried', array_merge( $context, array( 'delay_seconds' => self::retry_delay( $attempts ) ) ) ); return; } if ( $failure->temporary ) { Product_Saver::clear_pending( $product_id ); } Notices::add_failed_save( $product_id ); Logger::error( 'Pending save given up', array_merge( $context, array( 'reason' => $failure->getMessage() ) ) ); } } /** * Задача: проверка связи раз в сутки. * * Ключ стал неверным — отметка для уведомления в админке. Ключ работает — отметка * снимается. Временные неудачи (нет связи, сервис недоступен) только пишутся в журнал: * про ключ они ничего не говорят. * * @return void */ public function check_connection(): void { $client = Client::from_config(); if ( null === $client ) { return; } try { $client->get_connection(); Notices::clear_key_rejected(); } catch ( Api_Exception $exception ) { if ( Error_Kind::KeyInvalid === $exception->kind ) { Notices::flag_key_rejected(); } Logger::warning( 'Daily connection check failed', $exception->log_context() ); } } }