Отложенная публикация статьи. Instant 2.01

2728
Доброго всем времени суток!

Увидел просьбу на форуме о возможности отложенной публикации, и решил сделать, заодно изучив возможности расширения функционала второй версии.
Я хочу рассказать вам об этом в виде урока.

Мы будем делать отложенную публикацию только для типа контента "Статьи", думаю на все остальные типы контента можно сделать по аналогии.

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

Итак, нам необходимо при добавлении или редактировании статьи указать дату публикации статьи. Что нам для этого нужно?


Нам нужно в форму добавления статьи (и редактирования, что в принципе одно и тоже) добавить два поля - одно признак, что статья отложена, второе - дата, когда статья должна опубликоваться.

Зайдем в административную часть, ТИПЫ КОНТЕНТА - СТАТЬИ - ПОЛЯ
добавим новое поле
Системное имя : seelater
Название поля: Опубликовать позже
Подсказка для поля: пишем что-нибудь по желанию

Тип поля: Флаг
Группа - Создать новую группу: Отложенная публикация вводим именно так как написано, это название будет использовано в скрипте, позже поймете зачем

Также, ставим Доступ для изменения - только группе Администраторы
все остальные поля в форме оставляем как есть

скрины
""
Изображение уменьшено. Щелкните, чтобы увидеть оригинал.

""
Изображение уменьшено. Щелкните, чтобы увидеть оригинал.


Итак у нас есть одно поле. В качестве поля для даты публикации будем использовать встроенное поле таблицы date_pub - это поле формата ДАТА без времени, поле со временем нужно добавлять как-то отдельно (в движке отсутствует). Поэтому статья будет опубликована только при наступлении указанной даты.

С полями мы закончили, теперь приступим к самому интересному

Мы будем использовать хуки (hooks) движка, чтобы внедрится в его функционирование. В файле /system/controllers/content/manifest.php добавим все наши хуки, которые мы будем использовать, чтобы движок Инстанта их увидел.

Добавьте в массив следующие строки
'content_articles_form',
'content_articles_after_add',
'content_articles_after_update',

чтобы получилось так:

Код PHP:
  1. <?php
  2.  
  3. return array(
  4.  
  5. 'hooks' => array(
  6. 'menu_content',
  7. 'user_delete',
  8. 'user_tab_info',
  9. 'user_tab_show',
  10. 'sitemap_sources',
  11. 'content_articles_form',
  12. 'content_articles_after_add',
  13. 'content_articles_after_update',
  14. )
  15.  
  16. );
  17.  
Здесь, content_articles_form хук для вывода формы добавления или редактирования статьи, соответственно, content_articles_after_add и content_articles_after_update - хуки вызываемые после добавления или редактирования статьи

Рассмотрим первый хук, создайте файл content_articles_form.php в папке /system/controllers/content/hooks со следующим содержимым

Код PHP:
  1. <?php
  2.  
  3. class onContentContentArticlesForm extends cmsAction {
  4.  
  5. const SEELATERNAME = "Отложенная публикация";
  6.  
  7. public function run($form){
  8.  
  9. $structure = $form->getStructure();
  10. // так как по нормальному fieldset выбрать не получится будем искать по title перебором
  11. foreach($structure as $ids=>$str) {
  12. if ($str['title']==self::SEELATERNAME) {
  13. $form->addField($ids, new fieldDate('date_pub', array(
  14. 'title' => 'Дата публикации'
  15. )));
  16. break;
  17. }
  18. }
  19.  
  20. return $form;
  21.  
  22. }
  23.  
  24. }
  25.  
Этот хук делает следующее - он сначала получает данные формы ( getSctructure() ) далее, ищет в них группу "Отложенная публикация", а в ней как вы помните уже находится поле флага отложенной записи, и если находит, добавляет к нему поле даты публикации date_pub. Далее, после отработки хука у нас появитсяя форма для ввода данных.

Далее, рассмотрим второй и сразу третий хук

Создайте в той же папке hooks еще два файла: content_articles_after_add.php и content_articles_after_update.php

Содержимое первого

Код PHP:
  1. <?php
  2.  
  3. class onContentContentArticlesAfterAdd extends cmsAction {
  4.  
  5. public function run($item){
  6.  
  7. if ($item['seelater']==1) {
  8. $now = date('Y-m-d');
  9.  
  10. $item['seelater'] = $now < $item['date_pub'] ? 1 : NULL;
  11. $item['is_private'] = $item['seelater'] == 1 ? 1 : 0;
  12. $ctype = $this->model->getContentTypeByName('articles');
  13. $fields = $this->model->getContentFields($ctype['name'], $item['id']);
  14. $item = $this->model->updateContentItem($ctype, $item['id'], $item,$fields);
  15. $user = cmsUser::getInstance();
  16. if ($user->is_admin) {
  17. cmsEventsManager::hook("content_after_add_approve", array('ctype_name'=>$ctype['name'], 'item'=>$item));
  18. }
  19. }
  20. return $item;
  21.  
  22. }
  23.  
  24. }
  25.  
и второго

Код PHP:
  1. <?php
  2.  
  3. class onContentContentArticlesAfterUpdate extends cmsAction {
  4.  
  5. public function run($item){
  6.  
  7. if ($item['seelater']==1) {
  8. $now = date('Y-m-d');
  9.  
  10. $item['seelater'] = $now < $item['date_pub'] ? 1 : NULL;
  11. $item['is_private'] = $item['seelater'] == 1 ? 1 : 0;
  12. $ctype = $this->model->getContentTypeByName('articles');
  13. $fields = $this->model->getContentFields($ctype['name'], $item['id']);
  14. $item = $this->model->updateContentItem($ctype, $item['id'], $item,$fields);
  15. $user = cmsUser::getInstance();
  16. if ($user->is_admin) {
  17. cmsEventsManager::hook("content_after_update_approve", array('ctype_name'=>$ctype['name'], 'item'=>$item));
  18. }
  19.  
  20. }
  21. return $item;
  22. }
  23.  
  24. }
  25.  
Веселые названия у классов, да? ну да ладно, это мелочи

Первый хук нужен нам для того, чтобы при добавлении статьи решить - будет статья опубликована сразу, или мы её еще "помурыжим". Смотрим значение поля seelater, если единичка, значит проверяем введенную дату публикации, если она больше сегодняшней - выключаем статью полем is_private, чтобы в activity (лента активности) запись пока не отображалась. Точнее она будет отображаться для друзей добавляющего, но у нашего автора друзей то как бы нет smile . К сожалению в активити нет статуса "показать только мне" или "никому", поэтому будем вставлять такие вот "костыли". После публикации статьи - is_private станет равно нулю - и статья в активити появится.

Далее, сохраняем статью еще раз, и вызываем хук от activity, соответственно

cmsEventsManager::hook("content_after_add_approve", array('ctype_name'=>$ctype['name'], 'item'=>$item));

либо при редактировании

cmsEventsManager::hook("content_after_update_approve", array('ctype_name'=>$ctype['name'], 'item'=>$item));

Странно, конечно, что результат работы хуков AfterUpdate и AfterAdd далее не используется, разработчик или забыл про него или не заметил (подсказка файлы item_add.php, item_edit.php
строки
cmsEventsManager::hook("content_after_add", $item);
cmsEventsManager::hook("content_{$ctype['name']}_after_add", $item);
или
cmsEventsManager::hook("content_after_update", $item);
cmsEventsManager::hook("content_{$ctype['name']}_after_update", $item);
)


Продолжим, теперь у нас при добавлении или редактировании статья обладает признаком отложенности. Как нам вывести правильно статьи, точнее, как нам не показать раньше времени неопубликованные (по нашему мнению) статьи.

Я предлагаю сильно не задумываться, а воспользоваться датасетами то есть Наборами:

Переходим опять в админку, ТИПЫ КОНТЕНТА - СТАТЬИ - НАБОРЫ

открываем набор ВСЕ

внизу добавляем фильтр по полю "Опубликовать позже" (наш флаг) ставим ему условие "Не заполнено", как на скрине



Тоже самое нужно сделать со всеми остальными наборами, чтобы они ненароком не показали нашу статью.

Что мы уже получили - мы можем не показывать статью до определенной даты. Если нам нужно показать её, мы просто убираем флаг отложенности - и она появляется. Но это вручную, а как сделать так, чтобы статьи публиковались сами.

Воспользуемся крон-задачами. То есть задачами по расписанию. Добавим последний хук в content, он то и будет публиковать наши статьи по наступлении указанной даты.

Итак добавляем файл cron_republic.php в папку /system/controllers/content/hooks (почему не нужно добавлять в manifest.php ? сам не знаю, так работает smile )

Код PHP:
  1. <?php
  2.  
  3. class onContentCronRepublic extends cmsAction {
  4.  
  5. public function run(){
  6.  
  7. $content_model = cmsCore::getModel('content');
  8.  
  9. $content_model->filterEqual('seelater', 1);
  10. $now = date("Y-m-d");
  11. $content_model->filterGTEqual('date_pub', $now);
  12. $items = $content_model->getContentItems('articles');
  13.  
  14. if (is_array($items)) {
  15. $ctype = $this->model->getContentTypeByName('articles');
  16. foreach($items as $item) {
  17. $item['seelater'] = $now < date('Y-m-d',strtotime($item['date_pub'])) ? 1 : NULL;
  18. $item['is_private'] = $item['seelater'] == 1 ? 1 : 0;
  19. $fields = $this->model->getContentFields($ctype['name'], $item['id']);
  20. $item = $this->model->updateContentItem($ctype, $item['id'], $item,$fields);
  21. cmsEventsManager::hook("content_after_update_approve", array('ctype_name'=>$ctype['name'], 'item'=>$item));
  22. }
  23. }
  24. }
  25.  
  26. }
  27.  
Далее заходим в админку - НАСТРОЙКИ - ПЛАНИРОВЩИК - СОЗДАТЬ ЗАДАЧУ и делаем как на скрине



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

Урок закончен, также можно сделать отложенную публикацию для всех остальных типов контента, либо написав отдельные хуки, либо сделать универсальные.

Напоследок скрин формы добавления статьи
""
Изображение уменьшено. Щелкните, чтобы увидеть оригинал.


По результатам - как мне показалось, доработки в данной версии Инстанта немного тяжеловаты. Может быть я еще не все в ней понимаю, но поля published в процессе очень не хватало smile
"Последние новости", простой модуль с выбором категорий | Инстант "по взрослому". Часть 1. Авторизация. Счетчик неверных входов. v1.10
Комментарии (21)
Ильгиз 6 июня 2014 в 09:48 -9
у него не должно быть друзей

Май 6 июня 2014 в 10:43 -2
чо за нах?
shaman888 6 июня 2014 в 11:14 0
наверно пикабушник smile
Ильгиз 6 июня 2014 в 11:53 0
Да.
Raiden 6 июня 2014 в 10:15 +1
Таня будет довольна)
Май 6 июня 2014 в 10:45 +1
Таня будет довольна)
Это главное. )
Ну и вообще, полезная вещь, обязательно воспользуюсь.
Большое спасибо, Крот!
Таня 6 июня 2014 в 20:46 0
Таня будет довольна)
Эх..
Я конечно все внимательно почитала и поняла, что почти ничего не поняла))
На самом деле, конечно, если бы этот вариант устраивал, то разобралась бы.
Кроту, конечно, спасибо за его попытки, но слишком много костылей.
Первое, то что надо быть только админом, не хочется всех авторов делать админами, к тому же у них не должно быть друзей...
Нет возможности назначать время. Получается что если есть 10 запланированных записей на 7 июня, то все они будут опубликованы в 00:00 ? Очень не удобно, когда нужно чтобы в течении дня выходили статьи, а не сразу все в 12 ночи назначенной даты.

В общем я буду смерено ждать, когда r2 возьмется за дело) Очень надеюсь, что эта функция будет нужным дополнением в instantcms и будет такой же простой и удобной, как сами знаете, где)
Def 6 июня 2014 в 20:50 +1
да, в коробке это было бы очень к месту
Крот 6 июня 2014 в 22:16 +1
я думаю что у r2 есть какое-то свое представление возможностей расширения данной версии cms. Я этого пока не увидел. Может быть маловато опыта.

Хотя, можно было бы изменить ядро и все бы работало - но нам ведь этого не нужно - cms должна помогать расширять функционал, а не ставить ограничения

p.s. в первой ветке, отложенные статьи можно сделать проще. наверное)
lokanaft 6 июня 2014 в 11:50 0
Вот только r2 предлагает не трогать файлы манифестов, а добавлять собственные контроллеры.
r2 6 июня 2014 в 12:20 +1
Верно. В идеале, это решение должно было быть оформлено как отдельный контроллер.
Потому что нет смысла использовать хуки, если файл манифеста системного контроллера все равно будет переписан при обновлении.
Крот 6 июня 2014 в 12:41 0
ок учту
но ИМХО странно как-то получается - делать контроллер чтобы хакнуть официально другой

контроллер - хакер smile
SJen 6 июня 2014 в 12:44 +1
не хакнуть, а хукнуть glasses
Крот 6 июня 2014 в 12:51 0
ага i`m a hooker glasses
r2 6 июня 2014 в 12:53 0
что в этом странного?
вам же не странно что в 1.10.3 делается плагин чтобы добавить функционал куда-либо?
в 2.0 просто нет "плагинов" в терминологии 1.x, их роль выполняют контроллеры (осознанно не использую слово "компоненты")
т.к. при ближайшем рассмотрении разница между "компонентами" и "плагинами" становится очень несущественной
Крот 6 июня 2014 в 16:10 0
Верно. В идеале, это решение должно было быть оформлено как отдельный контроллер.

в идеале эта возможность должна быть "из коробки" hoho
lokanaft 6 июня 2014 в 19:10 +1
В итоге, вроде бы простая задача, а упирается во всякие заковырки.
Raiden 6 июня 2014 в 20:50 +1
Видимо не такая уж и простая) Просто очень нужная.
webtotma 9 июня 2014 в 11:53 0
А такой вариант не прокатит?

Создаем доп.поле в типе контента - "Дата публикации", связываем с БД.
В шаблоне вывода контента парсим это поле на предмет сравнения с текущей датой. Если <=, то выводим
Если поле не заполнено, то публикуем как обычно.
Крот 9 июня 2014 в 12:45 +1
тогда cms при выводе списка будет "врать" про количество записей - нужно править перебором списка и "лечить" разбивку по страницам
также еще, статьи будут добавлены в активити, а их там быть не должно
также будут выведены в профиле

в принципе конечно можно, но будет много костылей
webtotma 9 июня 2014 в 14:37 +1
Все правильно подметили. Придется еще пару-тройку костылей доделывать.

Вообщем встраивать эту возможность надо в движок по дефолту.