Кратко: wp-cron — встроенный планировщик WordPress, который выполняет отложенные задачи: автопубликацию записей, проверку обновлений, рассылки, очистку корзины. Повторяющуюся задачу ставит функция wp_schedule_event(), одноразовую — wp_schedule_single_event(); интервалы настраиваются через фильтр cron_schedules, а сама работа выполняется функцией-обработчиком, привязанной к хуку через add_action(). Главная особенность: wp-cron срабатывает только при визитах на сайт, поэтому для точного расписания его переключают на системный cron хостинга константой DISABLE_WP_CRON. Ниже — полный код для новичков: свои интервалы, повторяющиеся и разовые задачи, таблица функций управления, диагностика «почему задачи не выполняются» и живой пример с еженедельным дайджестом.
Представьте будильник, который звенит не по времени, а только когда в комнату кто-то заходит. Примерно так устроен планировщик WordPress: это не демон, который тикает в фоне, а механизм, запускаемый при каждом обращении к сайту. Звучит странно, но на практике это решает главную проблему — планировщик работает на любом хостинге, где нет доступа к настоящему cron. Разбираемся, как ставить задачи, где их смотреть и что делать, когда они отстают.
Как работает wp-cron: будильник, который звенит при визите
Классический cron в Linux — это фоновый процесс, который просыпается каждую минуту и запускает команды по расписанию. В WordPress всё иначе. Когда посетитель открывает любую страницу сайта, ядро проверяет: есть ли в расписании задачи, время которых уже наступило? Если есть — WordPress делает сам себе фоновый HTTP-запрос (так называемый loopback) на файл wp-cron.php и сразу отвечает посетителю, не заставляя его ждать выполнения задач. Фоновый процесс уже разбирает очередь и запускает нужные хуки.
Расписание хранится в таблице wp_options в опции с именем cron. Это массив, где ключ — имя хука, а внутри — список «встреч» задачи с параметрами:
<?php
// Упрощённый вид записи в опции cron:
[
'ds_hourly_task' => [
[
'schedule' => 'hourly', // имя интервала
'args' => [], // аргументы, передаваемые хуку
'interval' => 3600, // пауза между запусками в секундах
],
],
]
] Отсюда два важных следствия. Первое: задачи идентифицируются по имени хука — это не просто «строка в очереди», а событие, к которому можно привязывать сколько угодно обработчиков. Второе: пока на сайт никто не заходит, время задач накапливается, и они выполнятся пачкой при первом же визите. Опоздание в полчаса-час для большинства задач незаметно, но именно с этим связана главная жалоба новичков: «я поставил задачу раз в минуту, а она выполняется раз в час». Это не баг, это архитектура.
Что WordPress уже запускает по расписанию
wp-cron не пустой по умолчанию — ядро само держит с десяток регулярных задач. Самые заметные:
wp_version_check,wp_update_plugins,wp_update_themes— проверки обновлений ядра, плагинов и тем;wp_scheduled_delete— окончательное удаление записей из корзины и отклонённых комментариев по истечении срока;publish_future_post— автопубликация записей, у которых в редакторе выбрана дата «в будущем» (это тоже cron-задача!);wp_privacy_delete_old_export_files— очистка устаревших файлов экспорта персональных данных;delete_expired_transients— уборка просроченных транзиентов (временного кэша опций).
Плюс каждый плагин может добавить своё: плагины резервного копирования ставят задачу «сделать бэкап», SEO-плагины — «обновить карту сайта», формы — «почистить старые черновики писем». Поэтому на живом сайте в расписании легко обнаружится 20–40 событий, и часть из них — «призраки» от давно удалённых плагинов. Хорошая новость: всё это видно и управляемо, о чём дальше.
Как посмотреть задачи: плагин WP Crontrol
Для новичков самый простой способ заглянуть в планировщик — бесплатный плагин WP Crontrol. Устанавливается из админки: «Плагины → Добавить новый», в поиске наберите Crontrol, активируйте. После установки появляется пункт меню «Инструменты → Cron Events», где видно всё расписание: имя хука, следующий запуск, интервал, и к какому файлу привязан обработчик.
Из этого же экрана можно:
- запустить любую задачу немедленно (кнопка Run now) — незаменимо для отладки;
- изменить интервал или время следующего запуска;
- удалить задачу, оставшуюся от удалённого плагина;
- добавить новую задачу и даже PHP-сниппет, который будет выполняться по cron, — без правки кода темы.
Альтернативы для сравнения:
| Способ | Кому подходит | Что умеет | Что нужно |
|---|---|---|---|
| WP Crontrol | Новичкам | Смотреть, запускать, менять, добавлять задачи | Установка плагина из админки |
WP-CLI: wp cron event list | Тем, у кого есть SSH | Тот же список в консоли, быстрый дебаг | SSH-доступ к хостингу |
Код: var_dump(wp_next_scheduled('хук')) | Точечная проверка | Время следующего запуска конкретного хука | Доступ к файлам темы |
Совет: Crontrol можно ставить и на рабочий сайт — плагин лёгкий и активен только в админке, но после настройки задач его не грех и отключить, если лишние пункты меню раздражают.
Свои интервалы: фильтр cron_schedules
Из коробки WordPress даёт три интервала: hourly (каждый час), twicedaily (дважды в сутки) и daily (раз в сутки). Нужен свой график — например, раз в 15 минут или раз в неделю — регистрируем его через фильтр cron_schedules:
<?php
add_filter( 'cron_schedules', 'ds_add_cron_intervals' );
function ds_add_cron_intervals( $schedules ) {
$schedules['ds_every_15_minutes'] = [
'interval' => 15 * 60,
'display' => 'Каждые 15 минут',
];
$schedules['ds_every_6_hours'] = [
'interval' => 6 * HOUR_IN_SECONDS,
'display' => 'Каждые 6 часов',
];
$schedules['ds_weekly'] = [
'interval' => 7 * DAY_IN_SECONDS,
'display' => 'Раз в неделю',
];
return $schedules;
} Три момента, на которых спотыкаются. Первый: ключ массива — это имя интервала, именно его вы потом передадите в wp_schedule_event(). Второй: interval считается в секундах — константы HOUR_IN_SECONDS, DAY_IN_SECONDS и WEEK_IN_SECONDS делают код читаемым. Третий: display — просто подпись, которую покажет WP Crontrol в выпадающем списке. Сам фильтр традиционно кладут в functions.php — мы уже собирали подборку полезных сниппетов для functions.php, планировщик логично дополнит коллекцию.
Повторяющаяся задача: wp_schedule_event + обработчик
Повторяющаяся задача ставится в два шага. Шаг первый — зарегистрировать событие: сказать планировщику «каждые N секунд дёргай хук ds_hourly_task». Шаг второй — повесить на этот хук обработчик, который будет выполнять реальную работу. Полный рабочий листинг:
<?php
// 1. Регистрируем задачу при загрузке WordPress.
add_action( 'wp', 'ds_schedule_hourly_task' );
function ds_schedule_hourly_task() {
// wp_schedule_event вернёт false, если событие уже запланировано,
// поэтому сначала проверяем, что задачи ещё нет.
if ( ! wp_next_scheduled( 'ds_hourly_task' ) ) {
wp_schedule_event( time() + 600, 'hourly', 'ds_hourly_task' );
}
}
// 2. Обработчик: здесь живёт реальная работа.
add_action( 'ds_hourly_task', 'ds_run_hourly_task' );
function ds_run_hourly_task() {
// Пример: считаем черновики и пишем в журнал ошибок.
$count = wp_count_posts( 'post' )->draft;
error_log( sprintf( '[ds-cron] Черновиков в блоге: %d', $count ) );
} Разбор по строкам. Регистрация привязана к хуку wp — он срабатывает после загрузки ядра на каждом запросе, и код каждый раз проверяет «а не запланирована ли уже задача». Конструкция if ( ! wp_next_scheduled(...) ) — это защита от дублей: без неё каждый визит добавлял бы ещё одну копию события в расписание. Параметр time() + 600 сдвигает первый запуск на 10 минут вперёд, чтобы задача не стартовала прямо в момент активации.
Куда класть этот код? Вариант для новичка — functions.php дочерней темы WordPress: при обновлении родительской темы правки не затрутся. Более «взрослый» вариант — собственный мини-плагин: задачи переживают смену темы, а код можно снабдить register_deactivation_hook(), чтобы расписание автоматически чистилось при деактивации. Про механику хуков мы подробно писали в статье про хуки WordPress: actions и filters — cron-события работают ровно по тем же правилам.
Одноразовые задачи: wp_schedule_single_event
Не всё должно повторяться вечно. Отправить письмо-напоминание через час после регистрации, удалить временный файл через сутки, разово синхронизировать каталог — для этого есть wp_schedule_single_event(). Разница принципиальная: событие выполняется один раз и само удаляется из расписания.
<?php
// Ставим разовую задачу: напоминание через час после публикации записи.
add_action( 'transition_post_status', 'ds_schedule_publish_reminder', 10, 3 );
function ds_schedule_publish_reminder( $new_status, $old_status, $post ) {
if ( 'publish' !== $new_status || 'post' !== $post->post_type ) {
return;
}
wp_schedule_single_event(
time() + HOUR_IN_SECONDS, // когда выполнить: через час
'ds_publish_reminder', // имя хука
array( $post->ID ) // аргументы для обработчика
);
}
add_action( 'ds_publish_reminder', 'ds_send_publish_reminder' );
function ds_send_publish_reminder( $post_id ) {
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return; // запись уже сняли с публикации — напоминание не нужно
}
wp_mail(
get_option( 'admin_email' ),
'Запись опубликована час назад',
'Проверьте карточку: ' . get_permalink( $post_id )
);
} Обратите внимание на третий параметр — массив аргументов. Он передаётся обработчику как отдельные параметры, и это единственный способ «протащить» в разовую задачу контекст (ID записи, e-mail, ID заказа). Полезный нюанс планировщика: если событие по каким-то причинам не выполнилось вовремя, WordPress даст ему второй шанс через 10 минут — защита от дублей при одновременных запросах. Поэтому не удивляйтесь, если разовая задача сработает с небольшой задержкой.
Управление задачами: шпаргалка функций
Весь API планировщика умещается в полдюжины функций. Держите под рукой:
| Функция | Что делает | Типичный пример |
|---|---|---|
wp_schedule_event( $time, $recurrence, $hook ) | Ставит повторяющуюся задачу | wp_schedule_event( time(), 'daily', 'ds_task' ) |
wp_schedule_single_event( $time, $hook, $args ) | Ставит одноразовую задачу | wp_schedule_single_event( time() + 3600, 'ds_once' ) |
wp_next_scheduled( $hook ) | Время следующего запуска (timestamp) или false | var_dump( wp_next_scheduled( 'ds_task' ) ) |
wp_get_scheduled_event( $hook ) | Полный объект события (интервал, аргументы) | Диагностика настроек задачи |
wp_unschedule_event( $time, $hook, $args ) | Снимает конкретную встречу задачи | Точечная отмена |
wp_clear_scheduled_hook( $hook ) | Удаляет все встречи хука | Чистка при деактивации плагина |
wp_reschedule_event( ... ) | Переназначает задачу с новым интервалом | Смена графика без снятия/постановки |
Правило гигиены: если код, который ставит задачу, может «уехать» (плагин деактивировали, тему сменили), задача остаётся в расписании и будет дёргать несуществующий хук. Поэтому в плагинах задачи принято убирать при деактивации:
<?php
// Внутри плагина: чистим расписание при деактивации.
register_deactivation_hook( __FILE__, 'ds_deactivate_plugin' );
function ds_deactivate_plugin() {
wp_clear_scheduled_hook( 'ds_hourly_task' );
wp_clear_scheduled_hook( 'ds_publish_reminder' );
}
// Быстрая проверка «жива ли задача» — удобно временно вставить
// в functions.php, открыть сайт и посмотреть результат:
// var_dump( wp_next_scheduled( 'ds_hourly_task' ) );
// int 1725734400 — задача есть, покажет время следующего запуска.
// bool false — задачи в расписании нет. Почему задачи не выполняются: диагностика
Вторая по популярности проблема после «как поставить» — «поставил, а оно не работает». Почти всегда причина из этого списка:
| Симптом | Причина | Что делать |
|---|---|---|
| Задачи выполняются с опозданием на минуты и часы | На сайт мало визитов — wp-cron нечему запускать | Перевести расписание на системный cron (раздел ниже) |
| Задачи не выполняются вообще, даже при визитах | Loopback-запросы WordPress к самому себе заблокированы (firewall, модуль безопасности, некорректный SSL) | Проверить доступность /wp-cron.php?doing_wp_cron напрямую, спросить поддержку хостинга |
| На локальной копии cron «мёртв», на живом сайте работает | Часть сборщиков локальной разработки отключает планировщик константой | Проверить wp-config.php на DISABLE_WP_CRON |
| Кэширующий плагин включён — задачи отстали | Посетителям отдаётся статика из кэша, запрос к wp-cron не запускается | Включить «системный cron» либо исключение для wp-cron.php; как кэш устроен — в статье про кэширование WordPress |
| Задача стоит, обработчик молчит | Опечатка в имени хука между wp_schedule_event и add_action, или фатальная ошибка внутри обработчика | Сверить имена символ в символ, включить журнал ошибок (код ниже) |
Для диагностики включите журнал: в wp-config.php перед строкой /* That's all, stop editing! */ добавьте:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // ошибки пишутся в wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false ); // на экране ничего не показываем После этого выполняйте задачу вручную через WP Crontrol (Run now) и смотрите wp-content/debug.log: фатальные ошибки в обработчике и предупреждения PHP проявятся там в явном виде. Кстати, если задача гоняет тяжёлые операции (массовое сжатие картинок, импорт), вынесите обработку порциями — у нас есть отдельный материал про оптимизацию изображений в WordPress, где ровно этот приём разобран.
Переводим wp-cron на системный cron хостинга
Если расписанию нужна точность (рассылки строго в 9:00, синхронизация каждые 5 минут) или на сайте мало трафика, выход один: пусть планировщик дёргает настоящий cron хостинга, а не визиты. Схема из трёх шагов.
Шаг 1. Отключаем «визитный» запуск. В wp-config.php (в корне сайта) добавьте строку выше комментария /* That's all, stop editing! */:
define( 'DISABLE_WP_CRON', true ); Константа не выключает сам планировщик — она лишь запрещает WordPress запускать wp-cron.php при визитах. Все зарегистрированные задачи остаются в расписании.
Шаг 2. Создаём задание в панели хостинга (в cPanel это «Задания cron», в панели Timeweb — «Планировщик заданий»). Частота — каждые 5 минут, команда:
wget -q -O - https://ваш-сайт.ru/wp-cron.php?doing_wp_cron >/dev/null 2>&1 или вариант с curl, если wget недоступен:
curl -s https://ваш-сайт.ru/wp-cron.php?doing_wp_cron >/dev/null Шаг 3. Проверяем: добавьте тестовую задачу раз в 5 минут (интервал из раздела 4), подождите один цикл и сверьте время выполнения в Crontrol. Отклонение в пределах минуты — норма.
Важно: правка wp-config.php — это правка боевого конфига. Перед изменениями сделайте свежий бэкап — мы пошагово описывали настройку резервного копирования WordPress именно для таких случаев. И ещё: PHP-код из этой статьи добавляйте в functions.php через файловый менеджер или FTP, а не через встроенный редактор темы — опечатка в коде планировщика с отключённым wp-cron чинится хуже. Базовые правила гигиены собраны в статье про безопасность WordPress для новичков.
Полный пример: еженедельный дайджест свежих записей на почту
Соберём всё вместе в законченную фичу: раз в неделю сайт собирает записи за последние 7 дней и отправляет администратору сводное письмо. Код можно вставлять в functions.php дочерней темы как есть — подставьте только свой e-mail в переменную $to, если хотите получать письма не на адрес администратора:
<?php
// 1. Интервал «раз в неделю».
add_filter( 'cron_schedules', 'ds_digest_intervals' );
function ds_digest_intervals( $schedules ) {
$schedules['ds_weekly'] = [
'interval' => WEEK_IN_SECONDS,
'display' => 'Раз в неделю',
];
return $schedules;
}
// 2. Регистрация задачи.
add_action( 'wp', 'ds_schedule_digest' );
function ds_schedule_digest() {
if ( ! wp_next_scheduled( 'ds_weekly_digest' ) ) {
// Первый запуск — в ближайшую субботу 10:00 по времени сервера.
$first = strtotime( 'next saturday 10:00' );
wp_schedule_event( $first, 'ds_weekly', 'ds_weekly_digest' );
}
}
// 3. Обработчик: собираем записи за неделю и шлём письмо.
add_action( 'ds_weekly_digest', 'ds_send_weekly_digest' );
function ds_send_weekly_digest() {
$query = new WP_Query( [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 20,
'date_query' => [
[ 'after' => '7 days ago' ],
],
'no_found_rows' => true,
] );
if ( ! $query->have_posts() ) {
return; // за неделю ничего не вышло — письмо не шлём
}
$list = '';
while ( $query->have_posts() ) {
$query->the_post();
$list .= sprintf(
"<li><a href='%s'>%s</a></li>",
esc_url( get_permalink() ),
esc_html( get_the_title() )
);
}
wp_reset_postdata();
$subject = sprintf(
'Дайджест «%s»: %d записей за неделю',
get_bloginfo( 'name' ),
$query->post_count
);
$body = '<p>Свежие материалы за неделю:</p><ul>' . $list . '</ul>';
wp_mail(
get_option( 'admin_email' ),
$subject,
$body,
array( 'Content-Type: text/html; charset=UTF-8' )
);
} Здесь собраны три приёма, которые стоит запомнить. strtotime('next saturday 10:00') задаёт «человеческое» время первого запуска вместо time(). date_query в WP_Query отбирает записи строго за период. А заголовок Content-Type: text/html в wp_mail() разрешает HTML-письма — без него теги поедут в письмо как текст. И да: если письма с сайта в принципе не доходят, начните с отладки почтовой подсистемы — в гайде про контактные формы WordPress есть пошаговая проверка отправки писем, а cron-рассылки без работающей почты — самый частый «сломанный» сценарий.
Бейдж с расписанием в админке: PHP + JS
Закрепим материал задачей посложнее: покажем в админке, когда выйдет следующий дайджест. Понадобится AJAX-эндпоинт на PHP и немного ванильного JavaScript — механика запросов без перезагрузки страницы та же, что в нашей статье про AJAX в WordPress.
<?php
// Подключаем скрипт только в админке.
add_action( 'admin_enqueue_scripts', 'ds_cron_badge_assets' );
function ds_cron_badge_assets( $hook ) {
if ( 'index.php' !== $hook ) {
return; // только на «Консоль»
}
wp_enqueue_script(
'ds-cron-badge',
get_stylesheet_directory_uri() . '/js/cron-badge.js',
array(),
'1.0',
true
);
wp_localize_script( 'ds-cron-badge', 'dsCron', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'ds_cron_nonce' ),
] );
}
// AJAX-эндпоинт: сколько осталось до следующего запуска.
add_action( 'wp_ajax_ds_cron_status', 'ds_cron_status' );
function ds_cron_status() {
check_ajax_referer( 'ds_cron_nonce', 'nonce' );
$next = wp_next_scheduled( 'ds_weekly_digest' );
if ( ! $next ) {
wp_send_json_error( 'not_scheduled' );
}
wp_send_json_success( [
'next_iso' => date( 'c', $next ),
'in_hours' => round( ( $next - time() ) / HOUR_IN_SECONDS, 1 ),
] );
} И сам скрипт js/cron-badge.js в папке дочерней темы — чистый ES5, без зависимостей:
(function () {
'use strict';
if (typeof dsCron === 'undefined') {
return;
}
var body = new XMLHttpRequest();
body.open('POST', dsCron.ajaxUrl, true);
body.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded'
);
body.onload = function () {
var resp;
try {
resp = JSON.parse(body.responseText);
} catch (e) {
return;
}
if (!resp || !resp.success) {
return;
}
var hours = resp.data.in_hours;
var label = hours < 48
? hours + ' ч.'
: Math.round(hours / 24) + ' дн.';
var badge = document.createElement('div');
badge.style.cssText =
'margin:15px 2px 2px;padding:12px;' +
'background:#fff;border-left:4px solid #2271b1;box-shadow:0 1px 1px rgba(0,0,0,.04)';
badge.textContent =
'Еженедельный дайджест выйдет через ' + label;
var target = document.getElementById('dashboard-widgets');
if (target) {
target.parentNode.insertBefore(badge, target);
}
};
body.send('action=ds_cron_status&nonce=' + encodeURIComponent(dsCron.nonce));
})(); Обратите внимание на три защитных приёма. check_ajax_referer() проверяет nonce — без него любой посетитель админки мог бы дёргать эндпоинт. Скрипт подключается только на нужной странице ($hook === 'index.php'), чтобы не грузить лишний JS по всей админке. А ответ сервера парсится в try/catch: даже если плагин кэширования подмешает в ответ HTML, админка не упадёт с фатальной ошибкой JS. Если интересен второй путь — REST-маршруты вместо admin-ajax, — у нас есть обзор REST API WordPress для новичков.
Частые вопросы о wp-cron
Чем wp-cron отличается от системного cron?
Системный cron — фоновый процесс сервера, который запускается строго по расписанию независимо от посещаемости. wp-cron — механизм WordPress, который проверяет расписание только при визитах на сайт и выполняет задачи через фоновый запрос к wp-cron.php. При низкой посещаемости wp-cron отстаёт, при точных графиках его переключают на системный cron через DISABLE_WP_CRON и задание в панели хостинга.
Можно ли ставить интервал меньше минуты?
Технически да — фильтр cron_schedules принимает любой интервал в секундах. Практически минимальный надёжный шаг для wp-cron — 5 минут: при каждом запуске планировщик ставит флаг-блокировку, и частые задачи начинают конкурировать между собой. Для секундных интервалов используйте системный cron, который дёргает wp-cron.php каждые 1–5 минут, или очереди на WP-CLI.
Куда вставлять код планировщика: в functions.php или в плагин?
Для одной-двух задач — в functions.php дочерней темы: правится в два клика и переживает обновления родителя. Для задач, которые должны жить независимо от темы (рассылки, синхронизации), — в собственный мини-плагин с register_deactivation_hook(): при деактивации расписание само почистится через wp_clear_scheduled_hook().
Как проверить, что задача попала в расписание?
Три способа: открыть «Инструменты → Cron Events» в WP Crontrol и найти хук в таблице; временно вставить var_dump( wp_next_scheduled( 'имя_хука' ) ) в functions.php — покажет timestamp следующего запуска или false; на хостинге с SSH выполнить wp cron event list через WP-CLI.
Хостинг блокирует loopback-запросы — что делать?
Проверить это просто: откройте https://ваш-сайт.ru/wp-cron.php?doing_wp_cron в браузере — страница должна отдать пустой ответ, а не ошибку 403/500. Если ошибка — пишите в поддержку хостинга с вопросом о фильтрации запросов к самому себе. Обходной путь тот же: системное cron-задание, которое вызывает wp-cron.php напрямую, не зависит от loopback-механики WordPress.
Безопасно ли отключать wp-cron через DISABLE_WP_CRON?
Да, если до этого настроено системное cron-задание из раздела 9. Константа лишь запрещает запуск планировщика при визитах — автопубликация записей, бэкапы и проверки обновлений продолжат работать через внешнее задание. Типичная ошибка новичка — включить константу и забыть про cron-задание: расписание остаётся, но ничего не выполняется, пока его не запустят вручную.

Вывод: wp-cron — это проще, чем кажется
Планировщик WordPress пугает звучанием «cron», но по сути это три функции и один фильтр. План первого знакомства:
- Поставьте WP Crontrol и посмотрите, что уже живёт в расписании вашего сайта — заодно почистите задачи удалённых плагинов.
- Зарегистрируйте свой интервал через
cron_schedulesи поставьте пробную задачу из раздела 5 — например, логирование раз в час. - Проверьте работу кнопкой Run now и журналом
debug.log, а не ожиданием «пусть само сработает». - Когда задачи пойдут в бой (рассылки, бэкапы, синхронизации), переведите планировщик на системный cron хостинга — это снимет зависимость от посещаемости.
wp-cron открывает те механики, которые заказчики обычно просят «как в больших проектах»: автоматические рассылки, отложенные публикации, регламентные чистки. Если вам нужен сайт с такой автоматизацией под ключ — посмотрите услуги разработки сайтов и примеры внедрений в портфолио, а календарь статей для разработчиков и заказчиков живёт в нашем блоге о разработке сайтов. Вопросы по конкретной задаче — через форму контактов.



