Идеальным решением был бы встроенный визуальный редактор Аннотации, но разработчики MODx встроили обычный textarea - не посчитали нужным предоставлять такую возможность. В результате редакторы сайтов могут воспользоваться следующими вариантами:
1. Писать только простой текст.
2. Вручную вписывать теги HTML. Это сложновато для простых пользователей.
3. Писать всё в основном визуальном редакторе, потом открыть режим HTML, выделить и вырезать в буфер нужное место в начале документа, закрыть HTML и вставить код в поле редактирования Аннотации. Тоже сложновато будет.
Можно ещё и вообще Аннотации не писать, а для вывода в новостных лентах использовать сниппеты, выводящие N символов из контента или часть контента до некоего скрытого разделителя, вставленного в виде комментария, например <!-- splitter -->. Это явный шаг назад (так мы делали в CMS Etomite, где понятия аннотации не было вообще).
Попробуем решить эту проблему.
Как визуально редактировать аннотацию
В MODx возможно создать Template Variable (TV) типа RichText. О TV мы ещё напишем несколько заметок. Пока же скажем, что TV - это некоторые дополнительные данные различных типов, не предусмотренные структурой данных документа. TV связываются с шаблонами (потому так и называются Template Variable). У документов, использующие эти шаблоны, появляются как бы дополнительные поля. Значения этих полей можно отображать на страницах сайта.
Создадим TV для редактирования текста. Делаем так:
1. Ресурсы - Управление ресурсами - вкладка Параметры (TV). Создаем новый параметр.
2. Устанавливаем свойства TV-параметра:
Присваиваем параметру имя annotation, тип ввода RichText и помечаем шаблоны, которые могут использовать это параметр.
Теперь открыв для редактирования документ, использующий шаблон, для которых разрешен этот TV мы обнаруживаем новый визуальный Редактор аннотации (заменитель для Introtext):
Он занимает всю верхнюю часть на вкладке Общие, "утапливая" вниз редактор контента. Это не очень-то удобно - запросто можно перепутать редактор TV и редактор контента и потом удивляться, почему же не виден измененный текст документа. Более удобной компоновкой редактора мы еще займемся с помощью плагина ManagerManager, а здесь разберемся, как же нам использовать текст, редактируемый в annotation.
Всё, что мы напишем в этом дополнительном редакторе, записывается в базу данных. Значение TV-параметра annotation мы можем использовать так же, как и других переменных документа, вставляя в шаблоны или чанки вызов [ *annotation* ]. Вот если бы мы не вставили на этой странице пробел между [ и *, то прямо в страницу вставился бы редактор TinyMCE, даже со стороны фронтенд.
Но нам надо не сам редактор, а текст, который в нём введен, поместить в поле introtext (Аннотация).Для этого придется сделать простой плагин.
Плагин для получения introtext
В идеале такой плагин должен:
1. При начале редактирования страницы взять текст из поля introtext и поместить в качестве значения в TV annotation.
2. При сохранении страницы поместить значение TV annotation в поле introtext.
3. Очистить значение TV annotation (просто чтобы базу данных не утяжелять).
Создаем новый плагин с именем, например, TvAnnoToIntro (для плагина имя значения не имеет). Привязываем плагин к событию OnBeforeDocFormSave. Сочиняем код плагина - пока самый примитивный.
Решение задачи 2 - заполнения introtext.
"Носком левой ноги мы давим окурок..." (С) фильм "Кавказская пленница".
global $introtext;
$annotation=$_POST['tvanotation'];
$introtext=$annotation;
Включаем плагин, редактируем TV annotation в TinyMCE, сохраняем страницу и? И видим, что ничего не видим. Поле introtext стало пустым, хотя раньше в нем что-то было, да и TV annotation не пустой. Ну, конечно же - детская ошибка, вместо tvannotation мы написали tvanotation.
Специально упоминаем про это - такая невнимательность может привести к длительным поискам причин сбоев.
Исправив опечатку мы получаем решение пункта 2 задания.
Замечание.
Сначала для получения значения TV annotation мы пытались использовать функцию getTemplateVar. Вот так:
global $introtext;
$tv = $modx->getTemplateVar('annotation');
$tv_content = $tv['value'];
$introtext = $tv_content;
Но выяснилось:
Если этот код использовать в сниппете, то всё работает. В переменной $tv_content оказывается содержимое TV.
Если этот код использовать в плагине, то выдается ошибка MySQL о неверных кавычках. При замене на
$tv = $modx->getTemplateVar(`annotation`); - изменили кавычки - ошибки нет, но нет и результата.
Плагин выполняется на событии OnBeforeDocFormSave. Знающие люди подсказали, что функция getTemplateVar использует переменные documentIdentifier и documentObject, которые не определены перед сохранением документа. Покопавшись в исходниках MODx мы установили, что в этот момент значение TV можно получить через $_POST['tvanotation'].
Решение задачи 1 - заполнения TV annotation.
"Носком правой ноги мы давим второй окурок..." (С) фильм "Кавказская пленница".
А это не так просто, как кажется. Нам нужно получить значение поля introtext и программно поместить в TV annotation. Сделать это надо в момент события... А черт его знает, какого. Документации нет. Судить можно только по названиям, да путем анализ исходников самой MODx и разных плагинов. Скорее всего это событие OnDocFormPrerender.
Удивительно, но в MODx вообще нет функции с именем наподобие SetTemplateVar. Но в репозитарии найдена библиотека document.class.inc.php с подходящими функциями. Автор, правда, её забросил, не обновляет, неизвестно как будет работать в нашей версии MODx. "Будем посмотреть".
В библиотеке есть класс Document и функции
- Get()/Set() - получение/установка полей документа. или TV, если указан префикс 'tv'.
- GetTV()/SetTV() - получение установка TV, то же, что и Set()/Get(), но с указанным префиксом.
- Save() - сохранение документа.
- Delete() - удаление документа вместе с TV
Вроде бы то, что надо
Для начала сделаем отдельный плагин IntroToTvAnno, привязанный к событию OnDocFormPrerender.
Вот такой код:
global $id;
@require_once $modx->config["base_path"].'assets/libs/docapi/document.class.inc.php';
$doc = new Document($id,'introtext');
$intro=$doc->Get('introtext');
if (!empty ($intro)){
$doc->SetTV('annotation',$intro);
$doc->Save();
}
В этом плагине загружается дополнительныя библиотека и используется определенный в ней класс Document. Класс имеет методы, отсутствующие в самой MODx. Они позволяют программно создавать и изменять документы. В нашем плагине мы считываем значение поля introtext и помещаем в значение TV-параметра annotation.
При включении этого плагина всё заработало, как надо! Но с небольшим нюансом - быстро перестало работать.
Вдруг, при попытке редактирования вот этой страницы стала вылетать ошибка MySQL со ссылками на плохой запрос.
Execution of a query to the database failed - ou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version or the right syntax to use near 'tvanotation'];
Ошибка появляется только при включенном плагине и только на отдельных страницах. Разумеется, мы протестировали возможные сочетания условий наличия или отсутствия "фигурантов" плагина - introtext и TV annotation. Всё работает! Но не на этой странице. Тогда мы вообще удалили содержимое поля content (в нем много подозрительных символов), и оказалось, что плагин работает! Дело было в том, что первоначально у нас в коде плагина было не
$doc = new Document($id,'introtext');
а
$doc = new Document($id);
При этом, если опущены имена полей, в документ загружаются все поля, а далее документ у нас сохраняется после обновления TV. Вот в этот момент содержимое поля content и имеет "неудобный" для SQL-запроса вид - в нем всякие кавычки и символы имеются. Когда мы включили в конструктор объекта Document только одно конкретное поле, ошибка пропала. Завяжем узелок на память.
Решение задачи 3 - очистить значение TV annotation
Очевидно, это надо делать после сохранения документа, когда значение TV уже попало в introtext. Вроде бы можно, при событии OnDocFormSave выполнить:
$doc = new Document($id,'introtext');
$doc->SetTV('annotation','');
$doc->Save();
Но так не получается, TV не очищается. Вернее, очищается - это видно, если отмечена опция После редактирования - закрыть. Но, если после сохранения редактирование продолжается, то наступает событие OnDocFormPrerender, и TV вновь заполняется нашим же плагином. Значит надо искать другое событие, а его пока не видим.
В общем, пока решения не нашлось. И вариант с прямым удалением из базы пробовали:
$tvValue = 7;
$modx->db->delete($modx->getFullTableName('site_tmplvar_contentvalues'), 'contentid="' . $id . '" AND tmplvarid="' . $tvValue . '"');
Нет, не получается. То есть удалить-то получается, но потом сразу значение восстанавливается. Пока этот вопрос оставим открытым. Разумеется, и $tvValue будем не константой использовать, а выяснять по имени TV
Наконец мы можем объединить временные плагины в один.
Общий плагин для синхронизации introtext и TV annotation.
"А теперь оба окурка мы давим вместе!" (С) фильм "Кавказская пленница".
Собираем всё в один плагин
global $introtext;
@require_once $modx->config["base_path"].'assets/libs/docapi/document.class.inc.php';
$e = &$modx->Event;
switch ($e->name) {
case "OnBeforeDocFormSave":
$annotation=$_POST['tvannotation'];
$annotation=trim($annotation);
$annotation = mysql_escape_string($annotation);
$introtext=$annotation;
break;
case "OnDocFormRender":
case "OnDocFormSave":
$tvValue = 7;
$modx->db->delete($modx->getFullTableName('site_tmplvar_contentvalues'),
'contentid="' . $id . '" AND tmplvarid="' . $tvValue . '"');
break;
case "OnDocFormPrerender":
$doc = new Document($id,'introtext');
$intro=$doc->Get('introtext');
if (!empty ($intro)){
$doc->SetTV('annotation',$intro);
$doc->Save();
}
break;
default :
return;
break;
}
Да, способ был найден. Для удаления TV мы воспользовались одновременно событиями OnDocFormRender и OnDocSave!. В результате,если при Сохранить включена опция Продолжать редактирование (а это чаще всего делается) TV удаляется из своей таблицы по событию OnDocFormRender, а при опции Отменить - по событию OnDocSave. В результате записи в таблице _site_tmplvar_contentvalues, относящиеся к TV annotation, появляются только на период редактирования страниц.
Впоследствии было найдено вообще очень простое решение, описанное в заметке Проблема с ManagerManager
Написать комментарий