Glossary overview

WP: Cron

WP-Cron — это не настоящий системный cron.

Он работает так:

  1. Пользователь открывает любую страницу сайта
  2. WordPress проверяет: “есть ли задачи, время которых уже наступило?”
  3. Если есть — запускает их прямо в этом же запросе (в фоне)

Пример

При сохранении опций в админке устанавливаем кастомную CRON задачу.

// Call
AutoBumpModule::register();

// Class
<?php

class AutoBumpModule
{
    private const CRON_HOOK = 'auto_bump_admin_profiles';

    public static function register(): void
    {
        add_filter('cron_schedules', [__CLASS__, 'addCustomCronSchedules']);
        add_action('init', [__CLASS__, 'scheduleCronEvent']);
        add_action(self::CRON_HOOK, [__CLASS__, 'autoBumpProfiles']);
        add_filter('acf/fields/post_object/result', [__CLASS__, 'addPostIdToTitle'], 10, 4);
    }

    public static function addCustomCronSchedules(array $schedules): array
    {
        $schedules['every_15_minutes'] = [
            'interval' => 15 * 60,
            'display'  => 'Every 15 Minutes',
        ];

        $schedules['every_30_minutes'] = [
            'interval' => 30 * 60,
            'display'  => 'Every 30 Minutes',
        ];

        $schedules['every_2_hours'] = [
            'interval' => 2 * 60 * 60,
            'display'  => 'Every 2 Hours',
        ];

        $schedules['every_3_hours'] = [
            'interval' => 3 * 60 * 60,
            'display'  => 'Every 3 Hours',
        ];

        $schedules['every_6_hours'] = [
            'interval' => 6 * 60 * 60,
            'display'  => 'Every 6 Hours',
        ];

        return $schedules;
    }

    public static function scheduleCronEvent(): void
    {
        $enabled   = get_field('sprmbl_auto_bump_enabled', 'option');
        $interval  = get_field('sprmbl_auto_bump_interval', 'option') ?: 'every_30_minutes';
        $timestamp = wp_next_scheduled(self::CRON_HOOK);

        if ($enabled) {
            if ($timestamp) {
                $currentSchedule = wp_get_schedule(self::CRON_HOOK);
                if ($currentSchedule !== $interval) {
                    wp_clear_scheduled_hook(self::CRON_HOOK);
                    wp_schedule_event(time(), $interval, self::CRON_HOOK);
                }
            } else {
                wp_schedule_event(time(), $interval, self::CRON_HOOK);
            }
        } elseif ($timestamp) {
            wp_clear_scheduled_hook(self::CRON_HOOK);
        }
    }

    public static function autoBumpProfiles(): void
    {
        $enabled = get_field('sprmbl_auto_bump_enabled', 'option');
        if (!$enabled) {
            return;
        }

        $profileIds = get_field('sprmbl_auto_bump_profiles', 'option') ?: [];
        $bumpCount  = (int) (get_field('sprmbl_auto_bump_count', 'option') ?: 2);

        if (empty($profileIds)) {
            return;
        }

        // Проверяем: если первая анкета в общем списке — это анкета админа, ничего не делаем
        if (self::isAdminProfileOnTop($profileIds)) {
            update_field('sprmbl_auto_bump_last_run', current_time('Y-m-d H:i:s') . ' (есть в топе)', 'option');
            return;
        }

        $activeProfiles = self::getActiveProfiles($profileIds);

        if (empty($activeProfiles)) {
            return;
        }

        // Сортируем: кто дальше от топа — первым
        usort($activeProfiles, function ($a, $b) {
            return $b['position'] - $a['position'];
        });

        $bumped = 0;
        foreach (array_slice($activeProfiles, 0, $bumpCount) as $profileData) {
            if (self::bumpProfile($profileData['id'])) {
                $bumped++;
            }
        }

        if ($bumped > 0) {
            UserCabinet::clearAllCache();
        }

        update_field('sprmbl_auto_bump_last_run', current_time('Y-m-d H:i:s') . " (поднято: $bumped)", 'option');
    }

    private static function isAdminProfileOnTop(array $adminProfileIds): bool
    {
        $query = new \WP_Query([
            'post_type'      => 'models',
            'posts_per_page' => 1,
            'post_status'    => 'publish',
            'orderby'        => 'modified',
            'order'          => 'DESC',
            'meta_query'     => [
                [
                    'key'     => 'model_active_before',
                    'value'   => date('Y-m-d'),
                    'compare' => '>=',
                    'type'    => 'DATE',
                ],
            ],
            'fields' => 'ids',
        ]);

        if (empty($query->posts)) {
            return false;
        }

        return in_array($query->posts[0], $adminProfileIds);
    }

    private static function getActiveProfiles(array $profileIds): array
    {
        $profiles = [];

        foreach ($profileIds as $postId) {
            $post = get_post($postId);
            if (!$post || $post->post_status !== 'publish' || $post->post_type !== 'models') {
                continue;
            }

            $activeBefore = get_field('model_active_before', $postId);
            if (empty($activeBefore) || strtotime($activeBefore) < strtotime('today')) {
                continue;
            }

            $position = UserCabinet::getPostPosition($postId);

            $profiles[] = [
                'id'       => $postId,
                'position' => $position ?? 999999,
            ];
        }

        return $profiles;
    }

    public static function addPostIdToTitle(string $title, \WP_Post $post, array $field, $postId): string
    {
        if ($field['key'] === 'field_699882cb2b0db') {
            $title = $title . ' (ID: ' . $post->ID . ')';
        }

        return $title;
    }

    private static function bumpProfile(int $postId): bool
    {
        $currentTime = current_time('mysql');

        $result = wp_update_post([
            'ID'                => $postId,
            'post_modified'     => $currentTime,
            'post_modified_gmt' => get_gmt_from_date($currentTime),
        ], true);

        UserCabinet::clearAllCache();

        return !is_wp_error($result);
    }
}

Схема:

Посетитель → страница → WP проверяет расписание → запускает autoBumpProfiles() 

Будет ли работать на локалке?

Да, но только если ты открываешь сайт в браузере. Сам по себе cron не тикает — нужен HTTP-запрос для его “пинга”.

Практически это значит:

    • Зашёл на сайт → через нужный интервал (15/30 мин и т.д.) при следующем визите — сработает
    • Если сайт никто не открывает — не сработает

    Как проверить что всё подключилось

    • Можно проверить через WP-CLI:
    wp cron event list

    После того как зайдёшь на сайт (чтобы scheduleCronEvent() отработал на init), в списке должно появиться событие auto_bump_admin_profiles.

    • Или запустить вручную:
    wp cron event run auto_bump_admin_profiles

    На продакшне обычно WP-Cron отключают (define(‘DISABLE_WP_CRON’, true)) и настраивают реальный системный cron.

    Тогда задачи выполняются строго по расписанию, не завися от трафика.

    Также можно смотреть CRON задачи в плагине WP Crontrol.