Главная » Функционал » PHP и API » Интеграция «Типографа» в редактор материалов. Часть 1

Интеграция «Типографа» в редактор материалов. Часть 1

03.02.2012 в 00:59
Автор: Heritor   

Какое-то время назад, находясь под впечатлением от некоторых глав книги «Ководство», я решил внимательнее относиться к форматированию текста в материалах, которые публикую. Всё началось с того, что я сохранил в отдельный текстовый файл знак длинного тире и символы кавычек-ёлочек. (Точнее, всё началось с того, что я стал строить предложения таким образом, чтобы избегать случаев, когда требуется использовать эти символы. Но эта пора быстро прошла.) Каждый раз, создавая новый материал, я открывал этот файл и методом «copy-paste» вставлял в текст нужные символы. Эта период закончился тоже довольно-таки быстро. И дело не в том, что мне надоело расставлять «все эти значки, кавычки и неразрывные пробелы вручную» — просто я наткнулся на «Типограф», разработанный «Студией Лебедева». Этот инструмент позволяет добавить текст в специальную форму на сайте студии и получить приведённый в порядок текст одним кликом мыши.

Я пользуюсь «Типографом» уже около года, и до этого момента меня процесс работы устраивал. Я конечно был в курсе, что есть соответствующий веб-сервис, работающий с языком WSDL, использующим протокол SOAP на базе XML-формата, но руки до внедрения этого сервиса на сайт никак не доходили. Сделаю лирическое отступление и признаюсь: они и не дошли бы, не задумай я написать данное руководство.

Из предыдущего абзаца можно сделать вывод, что я даже представления не имею, как всё это сделать. И это будет очень правильный вывод — я действительно пока что не знаю, как «добавить в редактор материала кнопку, которая будет „типографить“ выделенный текст». Однако, раз уж вы читаете данное руководство, значит у меня что-то получилось.

Исследование вопроса

Мой небольшой жизненный опыт всегда диктует одно: «Если компания производит качественную соль, это не значит, что и спички она будет выпускать отменного качества». Опираясь на это, я решил не использовать сразу же WSDL-сервис студии Лебедева, а задумал подробнее исследовать рынок веб-сервисов для экранной типографики — тех, которые могут принять запрос с текстом необработанным, вернув мне текст обработанный. И, прочитав несколько обзоров и просмотрев одну публичную порку типографов сводную таблицу с результатами тестирования типографов, я решил остановиться на сервисе «Типограф» Евгения Лепёшкина.

На странице с описанием представлен пример того, как можно организовать работу с этим сервисом с помощью PHP. Найдя в этом смысле точку опоры и принимая во внимание главную идею данного руководства («добавить в редактор материала кнопку, которая будет „типографить“ выделенный текст»), можно попробовать разделить работу на несколько этапов:

Первым этапом будет разработка PHP-файла, который позволит обращаться к веб-сервису, получая от него обработанный с помощью типографики текст. (Хотя это и становится понятно из повествования, я отдельно упомяну, что для успешного завершения этого мероприятия у вас должна быть подключена услуга «Возможность использования PHP-скриптов».)

Вторым этапом станет добавление на панель редактора материалов кнопки, которая будет «типографить» выделенный текст. Сюда можно отнести не только создание внешнего вида этой кнопки, но и всю механику процесса отправки запроса PHP-скрипту, получение результата и вставку данных в текст.

Реализация

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

Этап первый. Разработка PHP-файла

Зайдя по FTP на PHP-раздел своего сайта, в папке «scripts» можно создать отдельную папку «typograf». В ней создаётся файл typograf.php. Таким образом, получится вызывать его на исполнение, обращаясь по адресу: «http://мой.сайт/php/typograf/example-utf.php».

Изучив руководство по взаимодействию с веб-сервисом «Типографа», можно заметить, что отправка запроса и получение данных происходит с помощью сокета, который открывается функцией fsockopen(). Такой вариант на серверах Ucoz использовать не получится. Некоторое время назад, разрабатывая скрипт генерации XML-файла, подходящего для отправки на сервис Яндекс.Новости, я впервые столкнулся с тем, что эта функция не работает на хостинге Ucoz. Но можно вместо этого использовать библиотеку cURL, хотя я стараюсь её не использовать, когда это возможно (эта библиотека иногда ведёт себя странным образом, прерывая работу скриптов на ровном месте, однако, это справедливо для скриптов, в процессе работы которых HTTP-запросы выполняются несколько раз; в нашем случае во время работы скрипта cURL-запрос будет выполняться один раз, так что надо надеяться на стабильность работы).

После портирования скрипта для использования с cURL, содержимое файла typograf.php будет выглядеть следующим образом:

Листинг №1. Файл typograf.php
  1. <?
  2. if ($_POST['text'])
  3. {
  4.     $text = html_entity_decode(preg_replace('/\%u([a-f0-9]{4})/i', '&#x$1;', $_POST['text']), ENT_NOQUOTES, 'UTF-8');
  5.     $text = urldecode($text);
  6.     $text = stripslashes($text);
  7.    
  8.     $typoSettings = <<<MYXML
  9. < ?xml version="1.0" encoding="UTF-8" ?>
  10. <preferences>
  11.     <paragraph insert="0"></paragraph>
  12.     <acronym insert="0"></acronym>
  13. </preferences>
  14. MYXML;
  15.    
  16.     $postBody = 'chr=UTF-8&text=' . urlencode($text) . '&xml=' . urlencode($typoSettings);
  17.    
  18.     $host = 'http://www.typograf.ru/webservice/';
  19.    
  20.     $ch = curl_init();
  21.     curl_setopt($ch, CURLOPT_URL, $host);
  22.     curl_setopt($ch, CURLOPT_FAILONERROR, 1);
  23.     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  24.     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  25.     curl_setopt($ch, CURLOPT_TIMEOUT, 9);
  26.     curl_setopt($ch, CURLOPT_POST, 1);
  27.     curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
  28.     $typografResponse = curl_exec($ch);
  29.     curl_close($ch);
  30.    
  31.     print $typografResponse;
  32. }
  33. ?>

Обратите внимание на пробел во второй позиции строки 9. В тексте программы его быть не должно!

Вы наверняка заметили предыдущий абзац, выполненный в ненавязчивом стиле? Дело в том, что браузеры могут неадекватно реагировать на сочетание «<» и «?xml», записанные без пробела, даже если угловая скобка записывается в виде «&lt;». Поэтому в листингах приходится ставить между ними пробелы, иначе браузер будет пытаться воспринимать следующий за этим текст как XML-файл.

Строки 4-5 были добавлены в этот листинг в процессе работы над вторым этапом реализации — «Внедрение кнопки в редактор материала». Дело в том, что (если забежать немного вперёд) отправка POST-запроса из JavaScript с JQuery — это очень нежный вопрос, и при малейшей ошибке при подготовке этого запроса начинаются капризы: от отправки в запросе строки, которую PHP-скрипт никогда не поймёт, до просто отказа вообще что-нибудь отправлять. Чтобы избежать таких капризов, в JavaScript был использован метод escape(), который конвертирует строку в формат Unicode, и только после этого она отправлялась POST-запросом. Как следует из описания функции escape(), все символы, числовые значения которых меньше 255, кодируются как %xx, где xx — шестнадцатеричное представление соответствующего символа. Если значение кода символа больше 255, он кодируется в вид %uxxxx, где xxxx — шестнадцатеричный код символа. Однако, в PHP нет стандартной функции, которая могла бы преобразовать подобные последовательности символов в нормальную строку. Функция urldecode() подходит только для формата %xx, а функция html_entity_decode() работает только с кодами вида «&#xFFFF, где FFFF соответствует последовательности xxxx из %uxxxx. Поэтому и было решено изобрести подобный двухпроходный фильтр, как представлено в строках 4-5 листинга № 1. На первом шаге каждый xxxx-символ конвертировался в «&#xFFFF-формат и преобразовывался в обычный символ. На втором шаге в обычные символы конвертировался формат %xx.

Ещё один момент, на который стоило бы обратить внимание — переменная typoSettings (если кого-то удивляет способ, которым она определяется, это называется «Heredoc-синтаксис»). Веб-сервис «Типографа», который был выбран для использования, позволяет в запросе указать настройки. Среди них могут быть такие, как: «не добавлять знаки параграфа к тексту», «не добавлять в текст акронимы» и т.д. (Именно эти два параметра и задаются в листинге №1. Запрет на вставку параграфов сделан для того, чтобы была возможность, например, типографить только пару слов из предложения. Если такая настройка не будет задана, эти два слова превратятся в новый абзац.) Настройки должны представлять из себя корректный XML-файл. Для примера, ниже идёт листинг, который соответствует настройкам по умолчанию для веб-сервиса.

Листинг №2. Настройки веб-сервиса
  1. < ?xml version="1.0" encoding="UTF-8" ?>
  2. <preferences>
  3.     <tags delete="0">1</tags>
  4.     <paragraph insert="1">
  5.         <start><![CDATA[<p>]]></start>
  6.         <end><![CDATA[</p>]]></end>
  7.     </paragraph>
  8.     <newline insert="1"><![CDATA[<br />]]></newline>
  9.     <cmsNewLine valid="0" />
  10.     <dos-text delete="0" />
  11.     <nowraped insert="1" nonbsp="0" length="0">
  12.         <start><![CDATA[<nobr>]]></start>
  13.         <end><![CDATA[</nobr>]]></end>
  14.     </nowraped>
  15.     <hanging-punct insert="0" />
  16.     <hanging-line delete="0" />
  17.     <minus-sign><![CDATA[&ndash;]]></minus-sign>
  18.     <hyphen insert="0" length="0" />
  19.     <acronym insert="1"></acronym>
  20.     <symbols type="0" />
  21.     <link target="" class="" />
  22. </preferences>

Расшифровываются эти настройки примерно так (цитата из описания на сайте «Типографа»):

  • tags (теги) — значения: 0 — не расставлять; 1 — расставлять. Атрибут delete — значения: 0 — не удалять; 1 — удалять до типографирования; 2 — удалять после типографирования.
  • paragraph (параграфы) — атрибут insert: 1 — ставить; 0 — не ставить. start/end теги задают внешний вид обрамления параграфа, начальные и конечные теги соответственно (могут быть пустыми).
  • newline перевод строки. Атрибут insert: 1 — ставить; 0 — не ставить. Внутри тега пишутся теги перевода строки.
  • dos-text удаляет одинарные переводы строк и переносы. Атрибут delete: 0 — не удалять; 1 — удалять.
  • nowraped неразрывные конструкции. Атрибут insert: 1 — ставить; 0 — не ставить. Атрибут nonbsp: 0 — не использовать неразрывные конструкции вместо (неразрывного пробела); 1 — наоборот. Атрибут length: не объединять в неразрывные конструкции слова, написанные через дефис, с общей длинной больше N знаков. Если 0 то не используется. start/end аналогично параграфам.
  • hanging-punct висячая пунктуация. Атрибут insert: 1 — использовать; 0 — не использовать.
  • hanging-line висячие строки. Атрибут delete: 1 — удалять; 0 — не удалять.
  • minus-sign указывает какой символ использовать вместо знака минус: — &ndash; или &minus;.
  • acronym выделять сокращения. Атрибут insert: 1 — выделять; 0 — не выделять.
  • symbols как выводить типографированный текст. Атрибут type: 0 — буквенными символами (&nbsp;); 1 — числовыми (&#160;).
  • link добавляет дополнительные атрибуты к ссылкам

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

На этом можно завершить первый этап работы, так как «серверная» сторона готова и корректно функционирует: принимаются POST-запросы, которые передаются на веб-сервис «Типографа». Полученные данные возвращаются в формате JSON.

Этап второй. Внедрение кнопки в редактор материала


Рисунок 1. Принцип работы будет примерно такой

Разрабатывая идею и пересматривая первый сезон доктора хауса проводя исследования, я представлял себе что-то похожее на рисунок 1. PHP-часть уже готова, так что задача решена уже на целых 5%. Осталась какая-то малость — всего 95% работы.

В первую очередь придётся ещё немного времени провести в исследованиях. На этот раз понадобиться понять, каким образом можно получить доступ к панели кнопок, чтобы добавить ещё одну — свою, после чего прописать её функционал.

Все исследования будут проводиться в редакторе модуля «Новости сайта». Внедрение кнопки и функций, отвечающих за её поведение будет происходить в шаблоне. Не обязательно использовать только модуль «Новости сайта». Подойдёт любой. Для доступа к шаблону, отвечающего за внешний вид страницы редактирования материала, на панели Ucoz нужно выбрать пункт меню «Дизайн» → «Управление дизайном страницы» (в этот момент нужно находиться на странице редактирования материала). Чтобы обезопасить себя от появления будущего кода на остальных страницах сайта, имеет смысл добавлять его в следующей конструкции:

Листинг №3
<?if($PAGE_ID$=='edit' || $PAGE_ID$=='add')?>
<!-- Этот код будет появляться толька на страницах добавления или редактирования материала -->
<?endif?>

Это имеет смысл, так как в некоторых модулях страница редактирования использует не свой личный шаблон. Например, в модуле «Новости сайта» страница редактирования материала использует шаблон «Страница архива материалов».

Просматривая DOM-содержимое страницы редактирования, можно найти HTML-код, описывающий отдельную кнопку. Для кнопки, которая, к примеру, делает выделенный текст полужирным, этот код выглядит так:

Листинг №4
<table align="left" cellspacing="0" cellpadding="0" style="margin:0px">     <tbody>         <tr>             <td style="text-align:left;padding:0px;padding-right:0px;VERTICAL-ALIGN: top;margin-left:0;margin-right:1px;margin-bottom:1px;width:23px;height:25px;" unselectable="on">                 <div onmouseup="$msUp(event, 'oEditbrief', 'btnBoldoEditbrief')" onmousedown="$msDown(event, 'oEditbrief', 'btnBoldoEditbrief')" onmouseout="$msOut(event, 'oEditbrief', 'btnBoldoEditbrief')" onmouseover="$msOver(event, 'oEditbrief', 'btnBoldoEditbrief')" style="position:absolute;clip:rect(0px 23px 25px 0px);" id="btnBoldoEditbrief">                     <img title="Жирный" alt="Жирный" style="position: relative; top: 0px; left: 0px;" src="http://мой.сайт/panel/editor/icons/btnBold.gif" onmousedown="if(event.preventDefault) event.preventDefault();" unselectable="on">                 </div>             </td>         </tr>     </tbody> </table>

С одной стороны, это выглядит довольно-таки громоздко. С другой — именно в таком формате я и буду добавлять свою кнопку. Как говорится: «В чужой монастырь со своим уставом не лезут».

Тег <img>, описаный в этом листинге, представляет из себя спрайт, определённые части которого видны на экране в зависимости от конкретной ситуации. Части спрайта — картинки, размером 23 пикселя по ширине и 25 пикселей по высоте. Ориентируясь на это, я создал свой спрайт, который представлен ниже. В нём предусмотрены состояния: «обычный вид», «мышь наведена на кнопку», «кнопка мыши нажата», «кнопка отпущена» и «запрос обрабатывается». Так получилось, что второе сверху изображение не используется, но я его оставил на будущее. До этого планировалось, что оно будет обозначать состояние «мышь наведена на кнопку».


Рисунок 2. Дизайн будущей кнопки

Настала пора определиться с тем, что и куда нужно добавлять.

Во-первых, при загрузке страницы редактирования (если используется WYSIWYG-редактор) происходит инициализация классов для редактора и добавление table-элемента с идентификатором «idAreaoEdit*», который хранит в себе панель инструментов и редактируемую область. Для области с кратким описанием создаётся класс oEditbrief и таблица с идентификатором «idAreaoEditbrief». Для области с полным текстом материала — класс oEditmessage и таблица с идентификатором «idAreaoEditmessage». Используя адресацию в стиле JQuery, можно определить, где хранится содержимое верхней панели с кнопками, нижней панели с кнопками и редактируемой области.

Листинг №5. Адресация
$('div#oEditbrief table.istoolbar tr td table tr:eq(0) td').html() //Краткое описание - верхняя панель с кнопками $('div#oEditbrief table.istoolbar tr td table tr:eq(1) td').html() //Краткое описание - нижняя панель с кнопками $('iframe#idContentoEditbrief').html() //Краткое описание - редактируемая область $('div#oEditmessage table.istoolbar tr td table tr:eq(0) td').html() //Полный текст материала - верхняя панель с кнопками $('div#oEditmessage table.istoolbar tr td table tr:eq(1) td').html() //Полный текст материала - нижняя панель с кнопками $('iframe#idContentoEditmessage').html() //Полный текст материала - редактируемая область

Я решил добавить кнопку для типографики в верхнюю панель с кнопками. При этом, описывать я её буду похожим на листинг №4 образом.

Листинг №6
  1. <?if($PAGE_ID$=='edit' || $PAGE_ID$=='add')?>
  2.  
  3. <script type="text/javascript">
  4. var typoButtonAddress = '/template/btnTypograf.gif';
  5.  
  6. if ($('iframe#oEditbrief')) {
  7.     $('#oEditbrief table.istoolbar tr td table tr:eq(0) td').html($('#oEditbrief table.istoolbar tr td table tr:eq(0) td').html() + '<table align="left" cellspacing="0" cellpadding="0" style="table-layout:fixed;"><tbody><tr><td style="padding:0px;padding-left:0px;padding-right:0px;VERTICAL-ALIGN:top;margin-bottom:1px;width:5px;height:25px;" unselectable="on"><img width="5px" src="/panel/editor/icons/brkspace.gif" unselectable="on"></td></tr></tbody></table><table align="left" cellspacing="0" cellpadding="0" style="margin:0px"><tbody><tr><td style="text-align:left;padding:0px;padding-right:0px;VERTICAL-ALIGN: top;margin-left:0;margin-right:1px;margin-bottom:1px;width:23px;height:25px;" unselectable="on"><div onmouseup="typoMouse(' + "'oEditbrief', 'onmouseup')" + '" onmousedown="typoMouse(' + "'oEditbrief', 'onmousedown')" + '" onmouseout="typoMouse(' + "'oEditbrief', 'onmouseout')" + '" onmouseover="typoMouse(' + "'oEditbrief', 'onmouseover')" + '" style="position:absolute;clip:rect(0px 23px 25px 0px);" id="btnTypografoEditbrief"><img title="Оттипографить" alt="Оттипографить" style="position: relative; top: 0px; left: 0px;" src="' + typoButtonAddress + '" unselectable="on"></div></td></tr></tbody></table>');
  8.     oEditbrief.typoButtonState = new TypoState(0,0);
  9. }
  10.  
  11. if ($('iframe#oEditmessage')) {
  12.     $('#oEditmessage table.istoolbar tr td table tr:eq(0) td').html($('#oEditmessage table.istoolbar tr td table tr:eq(0) td').html() + '<table align="left" cellspacing="0" cellpadding="0" style="table-layout:fixed;"><tbody><tr><td style="padding:0px;padding-left:0px;padding-right:0px;VERTICAL-ALIGN:top;margin-bottom:1px;width:5px;height:25px;" unselectable="on"><img width="5px" src="/panel/editor/icons/brkspace.gif" unselectable="on"></td></tr></tbody></table><table align="left" cellspacing="0" cellpadding="0" style="margin:0px"><tbody><tr><td style="text-align:left;padding:0px;padding-right:0px;VERTICAL-ALIGN: top;margin-left:0;margin-right:1px;margin-bottom:1px;width:23px;height:25px;" unselectable="on"><div onmouseup="typoMouse(' + "'oEditmessage', 'onmouseup')" + '" onmousedown="typoMouse(' + "'oEditmessage', 'onmousedown')" + '" onmouseout="typoMouse(' + "'oEditmessage', 'onmouseout')" + '" onmouseover="typoMouse(' + "'oEditmessage', 'onmouseover')" + '" style="position:absolute;clip:rect(0px 23px 25px 0px);" id="btnTypografoEditmessage"><img title="Оттипографить" alt="Оттипографить" style="position: relative; top: 0px; left: 0px;" src="' + typoButtonAddress + '" unselectable="on"></div></td></tr></tbody></table>');
  13.     oEditmessage.typoButtonState = new TypoState(0,0);
  14. }
  15.  
  16. </script>
  17.  
  18. <?endif?>

Как видно из листинга выше, кнопки создаются только в том случае, если существует соответствующий iframe-элемент редактора. К примеру, если на странице нет редактора полного текста материала (oEditmessage), то и никаких действий для него производиться не будет.

В 4-й строке листинга №6 прописывается адрес изображения. Его можно сохранить в любую папку сайта (но не PHP-раздела).

Строки 7 и 12 листинга №6 добавляют кнопку для каждого из экземпляров редактора. При этом для неё будут прописываться события:

  • onmouseup = typoMouse('ЭкземплярКлассаРедактора', 'onmouseup')
  • onmousedown = typoMouse('ЭкземплярКлассаРедактора', 'onmousedown')
  • onmouseout = typoMouse('ЭкземплярКлассаРедактора', 'onmouseout')
  • onmouseover = typoMouse('ЭкземплярКлассаРедактора', 'onmouseover')

При вызове функции typoMouse() указывается имя экземпляра класса, чтобы все события и действия были привязаны к конкретному экземпляру редактора.

Дополнительно, к экземпляру JavaScript-класса редактора будет добавляться объект-свойство, которое хранит информацию: находится ли курсор мыши в данный момент над кнопкой, обрабатывается ли в данный момент выделенный текст. Они понадобятся в дальнейшем для корректной обратной связи от кнопки (строки 8 и 13).

В связи с ограничением на количество символов в одном материале, я продолжу рассказ в следующей части руководства - «Интеграция „Типографа“ в редактор материалов. Часть 2»

Типограф, материалы


Прикреплённый файл: 294_typograf.zip (5.8Kb)

Рейтинг: 5  (помогла ли Вам эта инструкция: да / нет)          Просмотров: 7087          Комментариев: 5

Похожие инструкции

11.03.2012 в 16:57      0  

В листинге 1 строка 2 может вызвать Notice, лучше использовать
Code
if (isset($_POST['text']))

строка 16
Code
$postBody = http_build_query(array('chr' => 'UTF-8, 'text' => $text, 'xml' => $typoSettings));
11.03.2012 в 17:43      0  

С исправлением листинга скорее всего могут возникнуть сложности - я писал инструкцию на HTML, при этом прав на редактирование HTML-кода у меня нет, и обе части стали нормально отображаться не с первого раза. Спасибо, что G-XPert терпеливо помогал с публикацией.

А ничего, если листинг останется таким же, а ваш комментарий станет мнением профессионала о моих скромных любительских попытках написать немного кода? smile
11.03.2012 в 18:03      0  

ничего smile
кстати, сейчас мне ничего не мешает не подключая услугу PHP, пользоваться обработчиком на вашем сервере. Может стоит добавить какой-то секретный ключ, чтобы закрыть доступ для левых сайтов?
11.03.2012 в 18:30      0  

Да, уж. О безопасности я подумал в последнюю очередь. sad

Надо попробовать написать третью часть руководства, посвящённую вопросам безопасности. Надеюсь, что её удастся опубликовать здесь же.

Вариант с секретным ключом - это не вариант. Его можно будет, при определённом старании, найти в HTML-коде страницы.

Навскидку, можно использовать три варианта:


  • Проверка $_SERVER['HTTP_REFERER'] на соответствие сайту. Это ненамного лучше секретного ключа, так как остаётся возможность, что злоумышленник подменит HTTP_REFERER.
  • Ограничение на IP-адреса пользователей скрипта. Это тоже как-то не совсем то.
  • Использование в PHP-коде проверки функцией ucoz_getinfo("SITEUSERID"), которая возвращает идентификатор пользователя - на том сайте, где располагается PHP-скрипт. Это самый предпочтительный вариант.

Странник

Спам
04.03.2012 в 03:18      0  

Изврат....