WP Cron не выполняется по истечении времени
Цель
Я хочу использовать wp_schedule_single_event()
для выполнения одиночного события, которое отправляет мне электронное письмо через 8 минут после отправки пользователем формы.
Проблема
Следующий код находится в моем functions.php
:
function nkapi_send_to_system( $args ) {
wp_mail( 'xxx', 'xxx', $args );
}
add_action( 'nkapi_send', 'nkapi_send_to_system' );
function schedule_event( $id ) {
wp_schedule_single_event( current_time( 'timestamp' ) + 480, 'nkapi_send', array( $id ) );
}
И следующий код используется для вызова schedule-event
:
schedule_event( $_SESSION['insert_id'] ); // переменная $_SESSION содержит число INT
После ожидания более 8 минут в моем почтовом ящике не было письма.
Что я пробовал
С помощью плагина Core Control можно увидеть, какие cron-задачи запланированы.
После нескольких изменений мне удалось настроить их довольно правильно, и что еще лучше, когда я нажимаю "Run Now", я действительно получаю письмо в свой почтовый ящик.
Но почему cron-задачи не выполняются, когда я посещаю свой сайт через 8 минут. Что может быть не так с этим кодом? Должен сказать, что это мой первый опыт использования WP Cron.
Я попробовал еще
После комментария vancoder я решил проверить, работает ли код, если поместить следующий код непосредственно в functions.php
:
function schedule_event( $id ) {
wp_schedule_single_event( time(), 'nkapi_send', array( $id ) );
}
if ( isset( $_SESSION['insert_id'] ) ) {
if ( ! array_key_exists( 'insert_scheduled', $_SESSION ) || $_SESSION['insert_scheduled'] != $_SESSION['insert_id'] ) {
schedule_event( $_SESSION['insert_id'] );
$_SESSION['insert_scheduled'] = $_SESSION['insert_id'];
}
}
Недостаток этого кода в том, что пользователь должен перейти на другую страницу, прежде чем этот код будет выполнен. Но с другой стороны, это тоже не работает, так что это не была бы моя первая проблема...
Сначала определите свои пользовательские расписания для cron-заданий.
add_filter('cron_schedules', array($this, 'cron_schedules'));
public function cron_schedules($schedules){
$prefix = 'cron_';// Избегаем конфликтов с другими cron-заданиями. Пример: cron_30_mins
$schedule_options = array(
'30_mins' => array(
'display' => '30 минут',
'interval' => '1800'
),
'1_hours' => array(
'display' => 'Час',
'interval' => '3600'
),
'2_hours' => array(
'display' => '2 часа',
'interval' => '7200'
)
);
/* Добавляем каждое пользовательское расписание в систему cron. */
foreach($schedule_options as $schedule_key => $schedule){
$schedules[$prefix.$schedule_key] = array(
'interval' => $schedule['interval'],
'display' => __('Каждые '.$schedule['display'])
);
}
return $schedules;
}
Вам нужно решить, где и когда запланировать событие.
Вот пример кода, который вызывает пользовательский метод класса:
$schedule = $this->schedule_task(array(
'timestamp' => current_time('timestamp'), // Определяем, когда запланировать задачу.
'recurrence' => 'cron_30_mins',// Выбираем одно из ранее заданных расписаний.
'hook' => 'custom_imap_import'// Устанавливаем имя вашей cron-задачи.
));
Вот код, который непосредственно планирует событие:
private function schedule_task($task){
/* Должна быть информация о задаче. */
if(!$task){
return false;
}
/* Устанавливаем список обязательных ключей задачи. */
$required_keys = array(
'timestamp',
'recurrence',
'hook'
);
/* Проверяем наличие необходимой информации. */
$missing_keys = array();
foreach($required_keys as $key){
if(!array_key_exists($key, $task)){
$missing_keys[] = $key;
}
}
/* Проверяем отсутствующие ключи. */
if(!empty($missing_keys)){
return false;
}
/* Задача не должна быть уже запланирована. */
if(wp_next_scheduled($task['hook'])){
wp_clear_scheduled_hook($task['hook']);
}
/* Планируем выполнение задачи. */
wp_schedule_event($task['timestamp'], $task['recurrence'], $task['hook']);
return true;
}
Теперь вам остается только вызвать имя вашей пользовательской cron-задачи. В этом примере имя задачи — custom_imap_import
.
add_action('custom_imap_import', array($this, 'do_imap_import'));
public function do_imap_import(){
// .... Выполняем действия при срабатывании cron ....
}
Таким образом, в этом примере $this->do_imap_import();
будет вызываться каждые 30 минут (при условии достаточного трафика на вашем сайте).
Примечания
Требуется посещение страницы, чтобы cron сработал в нужное время.
Пример: Если вы запланировали задачу с интервалом в 30 минут, но на ваш сайт никто не заходит в течение 4 часов, cron-задача не сработает, пока посетитель не зайдет на сайт через 4 часа. Если вам действительно нужно, чтобы задача выполнялась каждые 30 минут, рекомендуется настроить настоящий cron-запуск через хостинг-провайдера для посещения сайта с нужным интервалом.
Cron-задачи WordPress не замедляют ваш сайт!
Возможно, вы думаете: что если cron-скрипт выполняется долго, придется ли посетителям ждать его завершения? Нет! Как это возможно? Если посмотреть файл wp-cron.php
, вы найдете строку:
ignore_user_abort(true);
Это настройка php.ini
, которая означает, что если загрузка сайта/скрипта прервется, сам скрипт продолжит выполняться.
Если посмотреть файл wp-includes/cron.php
, вы найдете строку:
wp_remote_post( $cron_url,
array('timeout' => 0.01,
'blocking' => false,
'sslverify' => apply_filters('https_local_ssl_verify', true)) );
Это означает, что WordPress будет ждать только 0.01 секунды для запуска выполнения, затем прервет соединение, но так как ignore_user_abort
установлен в true
, скрипт продолжит выполняться. Эта функциональность — огромное преимущество для выполнения больших скриптов в cron-задачах WordPress.
Функции, которые могут помочь:

Это очень подробный ответ, который, насколько я могу судить, не затрагивает главный вопрос — почему все запланированные задачи (включая системные) завершаются с ошибкой.

Этот ответ направлен на то, чтобы направить пользователя в правильном направлении для понимания и корректной настройки запланированных задач (cron) в WordPress.

Это действительно помогло мне лучше разобраться в работе планировщика задач (cron) в WordPress.

В какой момент следует выполнять "add_action('custom_imap_import', array($this, 'do_imap_import'))", если речь идет о классе плагина? В конструкторе?

@codecowboy Да, или в том месте, где он может быть загружен, когда cron будет готов к выполнению.

Ядро уже предоставляет интервал hourly
, поэтому вам не нужно использовать 1_hours
.

Сначала, пожалуйста, подтвердите, что у вас не включены плагины кэширования? Плагины кэширования могут мешать работе cron-задач, так как ваши посетители получают не живую страницу, а её закэшированную версию.
Если у вас включён плагин кэширования, вы можете выбрать одну из ваших страниц, добавить исключение в настройках плагина кэширования для этой страницы, чтобы она никогда не кэшировалась.
Затем вам нужно будет вручную создать cron-задачу (используя cPanel, если вы находитесь на shared-хостинге, или через терминал, если это VPS/выделенный сервер), которая будет посещать эту страницу каждые несколько минут.
Надеюсь, это поможет!

WordPress Cron позволяет планировать задачи, но они выполняются только при наличии запроса к сайту. При каждом полученном запросе WordPress проверяет, есть ли задания cron для обработки, и если да, асинхронно отправляет запрос к /wp-cron.php?doing_wp_cron
для выполнения задачи. Если запрос не поступает в запланированное время, процесс cron не запускается.
Поскольку вы можете просматривать и запускать запланированные задания, возможно, что нет запросов, которые инициируют запуск cron, особенно если вы используете плагин кеширования. Лучший вариант для переноса этого на более регулярное расписание — отключить стандартную проверку в WordPress и использовать crontab
.
Сначала, чтобы отключить стандартную проверку (что может немного улучшить производительность на стороне клиента), добавьте следующее в wp-config.php
:
// Отключить стандартную проверку заданий cron WordPress при загрузке страниц
define( 'DISABLE_WP_CRON', true );
Затем создайте задачу для запроса страницы wp-cron.php
раз в минуту для обработки всех заданий на серверной стороне. В командной строке введите crontab -e
и добавьте строку, которая выглядит следующим образом:
*/1 * * * * /usr/bin/curl --silent http://example.com/wp-cron.php?doing_wp_cron=$(date +\%s.\%N) >/dev/null

Для тех, кто защищает свой (разрабатываемый) сайт от публичного доступа, HTTP-аутентификация может стать причиной неработоспособности WP Cron.
На случай, если это кому-то поможет, вот список действий, которые я выполнил перед тем, как выявил и понял требования WP Cron:
- Я заметил, что события корректно планировались и могли быть запущены с помощью WP-CLI.
- Также заметил, что доступ к /wp-cron.php?doing_wp_cron через браузер действительно запускал выполнение, как упоминалось, например, в 13625.
- Была прочитана официальная документация.
- Я убедился, что DISABLE_WP_CRON не установлена.
- И узнал, что ALTERNATE_WP_CRON является рабочим, но неудовлетворительным решением.
- Чтобы исключить регрессионные ошибки, я попробовал установить несколько старых версий WordPress.
- Все плагины были отключены, а тема изменена на стандартную, чтобы изолировать проблему.
- Люди упоминали термин loopbacks, но из-за отсутствия связанных ошибок или предупреждений я посчитал это несущественным.
- В конце концов, была найдена вопрос с ответом, который объяснял, как отладить cron.
- После подключения Xdebug к wp_cron() я действительно увидел, что выполнение завершалось в wp_remote_post(), что явно не сработало.
Когда я понял, что искать, я нашел эту отличную статью о причинах проблем с WP Cron от Джеффа Старра. В конце статьи он ссылается на свой плагин wp-cron-http-auth.

Убедитесь, что DISABLE_WP_CRON не установлен в вашем конфигурационном файле.
Если это не помогает, попробуйте отключить все плагины (кроме core control - хотя я бы рекомендовал использовать wp-crontrol) и проверьте, работают ли основные задачи. Если они работают, значит, у вас где-то есть конфликт с плагинами.
Аналогично, попробуйте переключиться на стандартную тему типа twentysomething.
Если ни один из этих вариантов не помогает, скорее всего, проблема на стороне хостинга.

Проверьте любой плагин, который скрывает WordPress.
Как понять, что проблема в этом?
- Перейдите по адресу http(s)://вашсайт.com/wp-cron.php Вы должны увидеть пустую страницу. Абсолютно пустую.
- Кроме того, в менеджере cron-задач вы должны увидеть время в поле "Следующее выполнение":
(не просто текст "В очереди" - а конкретное время - для некоторых записей "В очереди" иногда нормально, но если это единственное, что вы видите -> ваш cron не работает.)
+1. Не доверяйте плагинам, которые "проверяют работу cron" - например, плагин WP Cron status checker показывал, что cron работает. Но на самом деле это было не так.
Вывод: Если вы получаете ошибку 404 - отключите a) не только кэширующие плагины, как советуют другие b) но и любые плагины, скрывающие WordPress.
