=== Mix Of Expired Posts ===
Contributors: andrey-konovalov
Tags: expiration, content, schedule, cron, gutenberg, draft, auto-draft, post expiration, content lifecycle, timed content
Requires at least: 5.3
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 2.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Donate link: https://mixofme.ru/

Профессиональный плагин для автоматического истечения срока действия публикаций. Автоматически переводит записи в черновик и/или добавляет метку по наступлении заданной даты.

== 📝 Краткое описание ==

**Mix Of Expired Posts** — это мощное и гибкое решение для управления жизненным циклом контента в WordPress. Задайте дату истечения для любой записи — и плагин автоматически выполнит настроенное действие (перевод в черновик, добавление метки или оба действия сразу), когда срок наступит.

Идеально подходит для новостных сайтов, интернет-магазинов, сайтов вакансий, образовательных платформ и любых ресурсов, где контент имеет ограниченный срок актуальности.

== 🎯 Для кого этот плагин? ==

* **Новостные сайты и СМИ** — автоматическое скрытие устаревших новостей, срочных сообщений, прогнозов погоды.
* **Интернет-магазины** — скрытие товаров с закончившимися акциями, сезонных предложений,限时-скидок.
* **Сайты вакансий** — автоматическое закрытие вакансий после дедлайна.
* **Образовательные платформы** — скрытие записей о завершённых курсах, вебинарах, мероприятиях.
* **Корпоративные сайты** — управление сроками действия объявлений, пресс-релизов, внутренних новостей.
* **Блоги и медиа** — автоматическое архивирование временных публикаций.

== 💡 Примеры использования ==

**Пример 1. Акция в интернет-магазине**
Вы публикуете запись об акции «Чёрная пятница — скидки до 70%». В боковой панели Gutenberg устанавливаете дату истечения: 1 декабря, 23:59. В настройках плагина выбрано действие «Добавить метку и перевести в черновик». После наступления указанной даты запись автоматически скроется с сайта и получит метку «Expired», чтобы вы могли легко найти её в архиве.

**Пример 2. Вакансия с дедлайном**
HR-менеджер публикует вакансию «Frontend-разработчик» и указывает срок приёма резюме — 15 числа следующего месяца. По наступлении этой даты запись автоматически перейдёт в черновик, и кандидаты больше не смогут откликаться через форму на сайте (если форма привязана к публикации записи).

**Пример 3. Новостная заметка с ограниченной актуальностью**
Редактор публикует срочную новость о мероприятии, которое пройдёт в ближайшие 48 часов. В списке записей он видит жёлтый индикатор ⚠️ «Осталось менее 48 часов». После мероприятия запись автоматически уйдёт в черновик, а редактор получит уведомление в логах плагина.

== ✨ Ключевые возможности ==

🔹 **Полная поддержка Gutenberg**
Удобная боковая панель с компонентом `DateTimePicker` прямо в блочном редакторе. Дата сохраняется автоматически вместе с записью через REST API.

🔹 **Классический редактор (TinyMCE)**
Традиционный метабокс в правой колонке для тех, кто использует старый редактор или кастомные поля.

🔹 **Умный список записей**
В таблице записей появляется новая колонка **«Срок»** с визуальными индикаторами:
* ⏳ — Дата истечения в будущем (серый/нейтральный)
* ⚠️ — Осталось менее 48 часов (жёлтый предупреждающий)
* ❌ — Срок уже истёк (красный)
* — — Дата не задана (прочерк)

Рядом с индикатором есть кнопки:
* ✏️ — быстрое редактирование даты прямо из списка (без открытия записи)
* 🗑️ — очистка даты истечения одним кликом

🔹 **Массовые действия (Bulk Actions)**
Обработайте десятки записей одновременно:
* «Пометить как истёкшее» — немедленно применить действие (метка + черновик)
* «Очистить дату истечения» — удалить дату у всех выбранных записей

🔹 **Надёжный WP-Cron с защитой**
* Встроенный transient-lock предотвращает параллельные запуски (важно для больших сайтов).
* Поддержка стандартных интервалов: каждый час, два раза в день, раз в сутки.
* Пользовательский интервал в минутах (например, каждые 15 минут).
* Ограничение batch size (20–200 записей за проход) для защиты сервера от перегрузки.

🔹 **Подробное логирование**
* Последние события отображаются прямо на странице настроек.
* Опциональная запись в файл `wp-content/uploads/moep-logs/moep.log` (с автоматической защитой от листинга).
* Дублирование в `error_log` при включённом `WP_DEBUG`.

🔹 **Поддержка WP-CLI**
Обработка истёкших записей одной командой:
`wp moep expire`

🔹 **Безопасность**
* Проверка прав (capabilities) на всех уровнях.
* Nonce-верификация для AJAX-запросов.
* Санитизация всех входных данных.
* Изолированная директория для логов с `index.html`.

🔹 **Хук для разработчиков**
`do_action('moep_post_expired', $post_id, $action, $timestamp);`
Позволяет расширять функционал плагина (например, отправлять email-уведомления автору записи).

== 🔧 Установка ==

1. Скачайте архив плагина и распакуйте его.
2. Загрузите папку `mix-of-expired-posts` в директорию `/wp-content/plugins/` вашего сайта через FTP или файловый менеджер хостинга.
3. Перейдите в раздел **Плагины** в административной панели WordPress.
4. Найдите плагин **Mix Of Expired Posts** и нажмите **«Активировать»**.
5. После активации в меню **«Настройки»** появится новый пункт **«Mix Of Expired Posts»**. Перейдите туда для первичной настройки.

== ⚙️ Подробная настройка ==

=== Шаг 1. Основные параметры ===

**Тип контента**
Выберите, для какого типа записей будет работать плагин:
* Посты (по умолчанию)
* Страницы
* Кастомные типы записей (товары, портфолио, вакансии и т.д.)

**Название метки и Slug метки**
Задайте название и технический идентификатор метки, которая будет добавляться к истёкшим записям. По умолчанию: «Expired» / `expired`.
*Совет:* Если вы меняете slug у существующей метки, плагин автоматически создаст новую. Старая метка останется у уже обработанных записей.

**Действие при истечении**
Выберите, что делать с записью, когда наступит срок:
* **Перевести в черновик** — запись скроется с сайта, но останется в админке.
* **Только добавить метку** — запись останется опубликованной, но получит метку для фильтрации.
* **Добавить метку и перевести в черновик** (по умолчанию) — комбинированный вариант.

=== Шаг 2. Cron и производительность ===

**Cron расписание**
Как часто плагин будет проверять истёкшие записи:
* Каждый час
* Два раза в день
* Раз в сутки
* Пользовательский (укажите интервал в минутах)

**Batch size**
Количество записей, обрабатываемых за один проход cron. Рекомендуется:
* Малые сайты (до 1000 записей): 50–100
* Средние сайты (1000–10000 записей): 100–200
* Крупные сайты (более 10000 записей): 200–500
* Установите 0, чтобы снять ограничение (не рекомендуется).

=== Шаг 3. Логирование ===

**Ведение логов**
Включите запись событий в базу данных. Последние события будут отображаться на странице настроек.

**Максимум записей в логах**
Сколько последних событий хранить (от 5 до 500). По умолчанию — 50.

**Запись логов в файл**
Включите дополнительную запись в файл `wp-content/uploads/moep-logs/moep.log`. Удобно для отладки и анализа.

== 📅 Установка даты истечения ==

=== В Gutenberg (блочный редактор) ===

1. Откройте запись на редактирование.
2. В правой панели найдите раздел **«Дата истечения»** (или откройте через меню ⋮ → «Дата истечения»).
3. Используйте календарь и время для выбора нужной даты.
4. Изменения сохраняются автоматически вместе с записью.
5. Для очистки даты нажмите кнопку **«Очистить дату истечения»**.

=== В классическом редакторе ===

1. Откройте запись на редактирование.
2. В правой боковой панели найдите метабокс **«Установка срока активности»**.
3. Выберите дату и время в поле `datetime-local`.
4. Нажмите **«Обновить»** или **«Опубликовать»**.

*Важно:* Если указанная дата уже прошла на момент сохранения, действие (черновик/метка) применится немедленно.

== 📊 Мониторинг в списке записей ==

После активации плагина в таблице записей появляется колонка **«Срок»** с индикаторами:

| Индикатор |         Значение         |
|-----------|--------------------------|
| ⏳        | Дата истечения в будущем |
| ⚠️        | Осталось менее 48 часов  |
| ❌        | Срок уже истёк           |
| —         | Дата не задана           |

**Быстрое редактирование:**
Наведите курсор на индикатор, чтобы увидеть точную дату в подсказке. Нажмите ✏️, чтобы изменить дату прямо из списка (откроется модальное окно). Нажмите 🗑️, чтобы очистить дату.

== ⏰ Cron и производительность ==

=== Как работает WP-Cron ===

WordPress использует псевдо-cron, который срабатывает при каждом посещении сайта. На сайтах с высокой посещаемостью это работает отлично. На сайтах с низкой посещаемостью cron может срабатывать нерегулярно.

=== Настройка системного cron (рекомендуется) ===

Для гарантированной своевременности обработки настройте вызов `wp-cron.php` через системный cron сервера.

**Пример для crontab (Linux):**
Откройте crontab: `crontab -e`
Добавьте строку (запуск каждые 15 минут):
*/15 * * * * wget -q -O - https://your-site.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1


**Пример для cPanel:**
1. Перейдите в раздел «Cron Jobs».
2. Выберите интервал: «Once per 15 minutes» (или другой).
3. В поле Command введите:
wget -q -O - https://your-site.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1


**Пример для ISPmanager:**
1. Перейдите в раздел «Cron» → «Добавить задачу».
2. Расписание: каждые 15 минут.
3. Команда: `wget -q -O - https://your-site.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1`

*Совет:* После настройки системного cron добавьте в `wp-config.php` строку:
`define('DISABLE_WP_CRON', true);`
Это отключит встроенный WP-Cron и предотвратит дублирование запусков.

== 👨‍💻 Для разработчиков ==

=== Хуки (Actions) ===

**`moep_post_expired`**
Вызывается после обработки истёкшей записи.

Параметры:
* `$post_id` (int) — ID записи
* `$action` (string) — выполненное действие (`draft`, `tag_only`, `tag_and_draft`)
* `$timestamp` (int) — дата истечения (Unix timestamp)

Пример использования:
```php
add_action('moep_post_expired', function($post_id, $action, $timestamp) {
    $post = get_post($post_id);
    $author = get_userdata($post->post_author);
    
    // Отправить email автору
    wp_mail(
        $author->user_email,
        'Ваша запись истекла',
        sprintf('Запись "%s" была автоматически переведена в черновик.', $post->post_title)
    );
}, 10, 3);


=== Константы ===
MOEP_DIR — путь к директории плагина
MOEP_URL — URL плагина
MOEP_FILE — путь к главному файлу плагина
MOEP_VERSION — версия плагина
MOEP_LOG_FILE — путь к файлу логов


=== Мета-ключ ===
Дата истечения хранится в мета-поле moep_expiration_date (Unix timestamp).
Пример получения даты:
$expiration = get_post_meta($post_id, 'moep_expiration_date', true);
if ($expiration && $expiration < time()) {
    // Запись истекла
}


== ❓ Часто задаваемые вопросы (FAQ) ==
= Плагин сработает мгновенно, как только наступит время? =
Зависит от настройки cron. На сайтах с высокой посещаемостью WP-Cron срабатывает практически мгновенно. На сайтах с низкой посещаемостью рекомендуется настроить системный cron (см. раздел «Cron и производительность»).
= Где хранятся логи плагина? =
Последние события отображаются прямо на странице настроек плагина. Если включена опция «Запись логов в файл», они сохраняются в защищённой директории wp-content/uploads/moep-logs/moep.log.
= Что происходит с данными при удалении плагина? =
При удалении плагина через административную панель:
Удаляются настройки плагина
Удаляются задачи cron и транзиенты
Сами записи, их метаданные и файлы логов НЕ удаляются
Это сделано намеренно, чтобы предотвратить случайную потерю данных.
= Поддерживает ли плагин кастомные типы записей (Custom Post Types)? =
Да, вы можете выбрать любой публичный тип записи (страницы, товары WooCommerce, портфолио, вакансии и т.д.) в настройках плагина.
= Можно ли настроить разные действия для разных типов записей? =
В текущей версии действие применяется глобально ко всем записям выбранного типа. Поддержка индивидуальных правил для разных типов запланирована в будущих версиях.
= Плагин совместим с WPML / Polylang? =
Да, плагин работает с мультиязычными сайтами. Каждая языковая версия записи обрабатывается независимо.
= Что делать, если cron не срабатывает? =
Проверьте настройки cron в разделе «Настройки → Mix Of Expired Posts».
Нажмите кнопку «Запустить проверку сейчас» для ручного запуска.
Убедитесь, что на сайте настроен системный cron (см. раздел «Cron и производительность»).
Проверьте логи плагина на наличие ошибок.
= Можно ли экспортировать/импортировать даты истечения? =
Даты хранятся в стандартных мета-полях WordPress (moep_expiration_date), поэтому они автоматически экспортируются/импортируются через стандартный инструмент WordPress «Экспорт/Импорт».
= Плагин замедляет сайт? =
Нет. Плагин использует оптимизированные запросы к базе данных с no_found_rows и отключёнными кэшами мета/термов. Обработка происходит в фоне через cron и не влияет на скорость загрузки страниц.
= Как изменить порог предупреждения (48 часов)? =
В текущей версии порог жёстко зашит в коде (48 часов). Изменение порога запланировано в будущих версиях.
== 🐛 Решение проблем (Troubleshooting) ==
=== Cron не срабатывает ===
Симптом: Записи не переходят в черновик после наступления даты.
Решение:
Перейдите в «Настройки → Mix Of Expired Posts».
Нажмите кнопку «Запустить проверку сейчас».
Если сработало — проблема в WP-Cron. Настройте системный cron (см. выше).
Проверьте логи плагина на наличие ошибок.
=== Логи не записываются в файл ===
Симптом: Файл moep.log не создаётся или пустой.
Решение:
Убедитесь, что в настройках включена опция «Запись логов в файл».
Проверьте права на директорию wp-content/uploads/. Веб-сервер должен иметь права на запись (обычно 755 или 775).
Проверьте логи ошибок PHP (error_log) на наличие сообщений о проблемах с правами доступа.
=== Кнопка ✏️ не открывает модальное окно ===
Симптом: При нажатии на карандаш ничего не происходит.
Решение:
Откройте консоль браузера (F12) и проверьте наличие ошибок JavaScript.
Убедитесь, что плагин активирован и скрипты загружаются.
Попробуйте очистить кэш браузера.
=== Индикаторы не отображаются в списке записей ===
Симптом: Колонка «Срок» отсутствует или пуста.
Решение:
Убедитесь, что в настройках выбран правильный тип контента.
Проверьте, что у записей действительно задана дата истечения.
Попробуйте обновить страницу списка записей (Ctrl+F5).


== 📋 Системные требования ==
WordPress: 5.3 или выше
PHP: 7.4 или выше (рекомендуется 8.0+)
MySQL: 5.6 или выше (или MariaDB 10.1+)
Браузер: Современные версии Chrome, Firefox, Safari, Edge


== 📸 Скриншоты ==
Страница настроек — основные параметры, мониторинг cron, ручная проверка, логи.
Gutenberg сайдбар — панель выбора даты истечения в блочном редакторе.
Список записей — колонка «Срок» с индикаторами и кнопками быстрого редактирования.
Классический метабокс — поле выбора даты в старом редакторе.
Модальное окно — быстрое редактирование даты прямо из списка записей.


== 🔄 Changelog ==
= 2.0.0 =
Улучшение безопасности: Логи теперь сохраняются в изолированную директорию wp-content/uploads/moep-logs/ с автоматической защитой от листинга (index.html).
Улучшение архитектуры: Полная переработка локализации (i18n) для JavaScript и PHP. Устранены все хардкод-строки.
Удаление: Удалена настройка «Цвет индикатора», так как нативные эмодзи не поддерживают CSS-окрашивание.
Новое: Добавлена кнопка очистки даты (🗑️) прямо в списке записей.
Улучшение: Замена устаревшего current_time('timestamp') на современный time() для корректного сравнения меток.
Улучшение: Добавлена проверка wp_is_post_revision() при сохранении метабокса.
Улучшение: Расширенная очистка базы данных при удалении плагина (uninstall.php).
Улучшение: Добавлены PHPDoc-комментарии ко всем методам для лучшей читаемости кода.
Улучшение: Оптимизация запросов к базе данных с использованием no_found_rows и отключённых кэшей.
= 1.0.0 =
Первоначальный релиз плагина.


== ⬆️ Upgrade Notice ==
= 2.0.0 =
Крупное обновление архитектуры и безопасности. Логи теперь сохраняются в защищённой папке uploads. Удалена настройка цвета индикатора. Добавлена кнопка очистки даты. Рекомендуется сделать резервную копию базы данных перед обновлением.


== 🤝 Поддержка и обратная связь ==
Сайт разработчика: mixofme.ru
Сообщить о баге: Создайте issue в репозитории или напишите на сайт разработчика.
Предложить улучшение: Приветствуется! Свяжитесь с разработчиком через сайт.
Если плагин оказался полезным, пожалуйста, оставьте отзыв — это мотивирует продолжать разработку!

== 📄 Лицензия ==
Этот плагин распространяется под лицензией GPL v2 или более поздней версии.
https://www.gnu.org/licenses/gpl-2.0.html