Разработка расширений
С технической точки зрения расширение S2 — это папка с несколькими файлами. Имя папки должно совпадать с идентификатором расширения. Устанавливаемые расширения помещаются в папку _extensions
. Основной файл, присутствующий в каждом расширении, называется manifest.xml.
manifest.xml
Это обычный
Пример файла manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<extension for="S2" engine="1.0">
<id>example_extension</id>
<title>Пример расширения</title>
<version>0.1</version>
<description>Краткое описание расширения.</description>
<author>Иван Иванов</author>
<minversion>1.0a3</minversion>
<maxtestedon>1.0a5</maxtestedon>
<dependencies>
<dependency>another_extension</dependency>
</dependencies>
<note type="install">Это сообщение выводится перед установкой расширения.</note>
<install><![CDATA[
// Код установки
]]></install>
<uninstall><![CDATA[
// Код удаления
]]></uninstall>
<hooks>
<hook id="idx_start"><![CDATA[
// Подключение файла foobar.php из папки расширения
require $ext_info['path'].'/foobar.php';
]]></hook>
<hook id="idx_template_pre_replace"><![CDATA[
// Вызов функции из файла foobar.php
example_extension_foo_function();
]]></hook>
</hooks>
</extension>
Рассмотрим подробнее формат этого файла.
Заголовки
<?xml version="1.0" encoding="UTF-8"?>
Это объявление XML, в котором говорится о версии XML и кодировке. S2 работает с кодировкой
<extension for="S2" engine="1.0">
Здесь указывается движок (S2) и версия формата расширений. Версия может пригодиться в будущем, если движок существенно изменится и старые расширения станут несовместимыми. В настоящее время используется версия 1.0.
Информация о расширении
<id>example_extension</id>
Это идентификатор расширения. С его помощью S2 различает установленные расширения, поэтому идентификатор обязательно должен быть уникальным. Обратите внимание на то, что идентификатор расширения должен совпадать с именем папки расширения. Желательно использовать строчные латинские буквы и «_» (знак подчеркивания, без кавычек). Идентификатор не может быть длиннее 50 символов.
<title>Пример расширения</title>
Собственно, само название расширения. Можете придумать
<version>0.1</version>
Версия расширения. Придерживайтесь определенных правил нумерации расширений (см. ниже).
<author>Иван Иванов</author>
Ваше имя. Выдумывать не приходится :)
<minversion>1.0a3</minversion>
<maxtestedon>1.0a5</maxtestedon>
Минимальная версия движка, в которой заработает расширение, и максимальная версия, в которой оно было протестировано. Если движок слишком старый, в установке расширения будет отказано. Если расширение не протестировано на новой версии движка, при установке будет выдано предупреждение.
Сообщения пользователю
Перед установкой и удалением расширения можно показывать пользователю сообщения или предупреждения. Например, перед установкой можно напомнить пользователю о необходимости дополнительных действий, которые невозможно выполнить во время установки (индексация большого количества данных и т. д.). Перед удалением расширения можно еще раз предупредить пользователя о необратимом удалении данных.
Примеры сообщений:
<note type="install">Это сообщение выводится перед установкой расширения.</note>
<note type="install">И это тоже.</note>
<note type="uninstall">Это сообщение выводится перед удалением расширения.</note>
Зависимости
Здесь указывается список расширений, без которых ваше расширение не сможет работать. Это полезно, если вы делаете расширение расширения или подключаете служебное расширение, содержащее
<dependencies>
<dependency>another_extension</dependency>
</dependencies>
Если хотя бы одно из расширений из этого списка отключено или не установлено, в установке вашего расширения будет отказано. А еще расширения из этого списка нельзя будет отключить или удалить, пока установлено ваше расширение.
Установка
<install><![CDATA[
// Код установки
]]></install>
Здесь можно указать код, который будет выполнен при установке. Вы можете, например, создать новую таблицу в базе данных или добавить новую колонку к существующей таблице.
В коде установки можно проверять, соответствуют ли параметры окружения системным требованиям. Если из секции установки возвращается строка, установка расширения останавливается, а строка выводится пользователю как сообщение об ошибке. Например, если расширение не работает на PHP 4, код установки можно начать с проверки версии PHP:
<install><![CDATA[
if (version_compare(PHP_VERSION, '5.0.0', '<'))
return 'Error! This extension does not work on PHP 4. Please upgrade to PHP 5.';
// Теперь мы уверены, что версия PHP не ниже 5
]]></install>
Важное замечание, касающееся секции install
, состоит в том, что она выполняется не только при установке, но и во время обновления расширения. Поэтому перед однократными действиями (вроде создания таблиц) нужно проверять, действительно ли это первая установка, или расширение обновляется. Для этого можно просто проверять, существуют ли уже создаваемые объекты, или использовать константу EXT_CUR_VERSION
. Она определена, если расширение обновляется, и равна текущей установленной версии расширения.
Удаление
<uninstall><![CDATA[
// Код удаления
]]></uninstall>
Хуки
Хуки — это специальные «теги» в коде S2, вместо которых подставляются соответствующие фрагменты кода из manifext.xml. Обычно хук выглядит следующим образом:
($hook = s2_hook('idx_template_pre_replace')) ? eval($hook) : null;
Самое важное в такой записи — это имя хука. В этом примере приведен хук idx_template_pre_replace
. Он находится в файле index.php
(префикс «idx_»). Вместо того чтобы размещать index.php
, его нужно поместить под соответствующим именем в секцию hooks
manifest.xml.
<hooks>
<hook id="idx_start"><![CDATA[
// Подключение файла foobar.php из папки расширения
require $ext_info['path'].'/foobar.php';
]]></hook>
<hook id="idx_template_pre_replace"><![CDATA[
// Вызов функции из файла foobar.php
example_extension_foo_function();
]]></hook>
</hooks>
В этой секции должны быть описаны все хуки, используемые в расширении. Фрагменты <![CDATA[
и ]]>
). Они будут подставляться в код движка вместо соответствующих хуков. В этом примере первый хук idx_start
просто подключает $ext_info['path']
). А второй хук idx_template_pre_replace
вызывает функцию из подключенного файла.
С помощью хуков можно не только вызывать функции из внешних файлов, но и дополнять или заменять функциональность ядра S2.
Повторяющиеся хуки
Если несколько хуков имеют совпадающий код, их можно указать через запятую и избежать повторения кода.
<hooks>
<hook id="idx_start, ai_start"><![CDATA[
// Какие-то общие действия для сайта и панели управления
]]></hook>
</hooks>
Порядок выполнения хуков
Хукам можно назначать приоритет, что может быть полезно для предотвращения несостыковок с другими расширениями. Например, если ваш хук возвращает значение (через return), возможно, стоит сделать так, чтобы он сработал последним и не помешал выполнению таких же хуков других расширений.
Приоритет — это число от 1 до 10, где 1 соответствет выполняющемуся в первую очередь коду, а 10 — в последнюю. Если хуки двух расширений имеют одинаковый приоритет, порядок запуска определяется порядком установки. По умолчанию приоритет равен 5. Его можно указать явно:
<hooks>
<hook id="idx_start" priority="2"><![CDATA[
// подключаем файл из папки расширения
require $ext_info['path'].'/foobar.php';
]]></hook>
</hooks>
Особенности разработки расширений
Использование префиксов
Чтобы предотвратить конфликты имен объектов, добавляйте префикс в виде идентификатора расширения к названиям функций, глобальных переменных, хуков в глобальной области видимости, новых таблиц базы данных, новых полей в имеющихся таблицах, fn_<название функции>_
.
Каждый разработчик может добавлять свой префикс к идентификаторам своих расширений. Не используйте префикс «s2_», он обозначает «официальные» расширения.
Массив $ext_info
В хуках можно использовать переменную $ext_info
, содержащую информацию о расширении:
$ext_info['id']
-
Идентификатор текущего расширения, например,
example_extension
. $ext_info['path']
- Путь к папке расширения относительно корня сайта, например,
./_extensions/example_extension
. Полезно при подключенииPHP-файлов. $ext_info['url']
- URL папки с расширением, например,
http://example.com/_extensions/example_extension
. Полезно при подключении к страницам стилей и картинок.
Область видимости хуков
Хуки встречаются в исходном коде S2 как в глобальной области видимости, так и внутри функций. В последнем случае код хуков выполняется в локальной области видимости. Это значит, что для обращения к глобальным переменным может потребоваться использование директивы global
или массива $GLOBALS
.
Массив $ext_info
определяется в текущей области видимости хука. Это значит, что при вызове $ext_info
даже при помощи директивы global
.
Подключение файлов
При разработке расширений рекомендуется не оставлять большие фрагменты
Эта рекомендация обусловлена соображениями производительности. Код хуков хранится в файле _cache/cache_hooks.php
и выполняется с помощью eval()
. Чем меньше кода в хуках, тем меньше накладных расходов при подключении этого файла и тем быстрее выполняются конструкции eval()
. К тому же подключение кода в include
или require
оптимизируется акселератором (акселераторы присутствуют в большинстве установок PHP). Другой аргумент заключается в упрощении отладки (см. ниже).
Обратите внимание на то, что рекомендация относится только к хукам, а не к коду установки или удаления. Более того, код удаления расширения нельзя выносить во внешние файлы. Дело в том, что код удаления вместе с другой информацией о расширении сохраняется во время установки в базе данных. При соблюдении этого требования расширение может быть корректно удалено даже в том случае, если на диске файлов расширения уже нет.
return внутри хуков
Значение, возвращаемое с помощью конструкции return
, может обрабатываться в некоторых хуках. Рассмотрим для примера фрагмент файла index.php
:
$return = ($hook = s2_hook('idx_get_content')) ? eval($hook) : null;
if (!$return)
{
if (!defined('S2_ARTICLES_FUNCTIONS_LOADED'))
require S2_ROOT.'_include/articles.php';
s2_parse_page_url($request_uri);
}
Если из хука ничего не возвращается (именно это обычно и происходит), вызывается функция s2_parse_page_url()
, которая анализирует URL, получает из базы нужные данные и формирует страницу сайта. Если из хука вернуть true
, ничего из описанного не произойдет. Такое поведение полезно при самостоятельном формировании дополнительных страниц.
Например, если в расширение добавить следующий хук, при обращении по адресу <адрес сайта>/secret_url
мы увидим сообщение Secret text
.
<hooks>
<hook id="idx_get_content"><![CDATA[
if ($request_uri == '/secret_url')
{
$page = array('text' => '<p>Secret text</p>');
$template = s2_get_service_template();
return true;
}
]]></hook>
</hooks>
Языковые файлы
Желательно, чтобы расширения поддерживали смену языка. Если расширение содержит строки, которые можно переводить, они должны быть вынесены в языковой файл. Путь к языковому файлу может быть таким: _extensions/<ext_id>/lang/<Language>.php
, например, _extensions/example_extension/lang/English.php
. Сами строки лучше объединить в массив:
<?php
$lang_example_extension = array(
'Example 1' => 'An example language string',
);
Для подключения языкового файла можно использовать такой код:
if (file_exists($ext_info['path'].'/lang/'.S2_LANGUAGE.'.php'))
require $ext_info['path'].'/lang/'.S2_LANGUAGE.'.php';
else
require $ext_info['path'].'/lang/English.php';
Номера версий
Номера версий расширений должны быть построены по тому же приципу, что и у S2, например 1.2.3. Здесь главное число — 1, второстепенное число — 2, номер выпуска — 3. Главное число увеличивается при полном переписывании, второстепенное — при добавлении функциональности, номер выпуска — после исправления мелких ошибок. Альфа- и
Расширения должны быть отключаемыми
Расширения можно отключить из панели управления или с помощью константы S2_DISABLE_HOOKS. При этом код удаления расширений не вызывается, отключение касается только хуков. Нужно помнить об этом при разработке расширений. В частности, нельзя вносить разрушительные изменения в базу данных (удалять стандартные таблицы или параметры настройки, изменять форматы полей и т. д.).
Расширяемые расширения
Если вы планируете распространять будущее расширение, подумайте о задаче, которую оно будет решать. Желательно взять только одну задачу, но решить ее хорошо. Сложную систему можно разбить на несколько связанных расширений с применением механизма зависимостей. Например,
Расширения сами по себе могут быть расширены. Добавляйте хуки в код своих расширений, чтобы другие разработчики смогли изменить их функциональность. Хуки расширений работают точно так же, как и обычные хуки. Чтобы убедиться, что расширяемое расширение установлено, можно использовать зависимости в manifest.xml.
Отладка
Комментарии
В документации списка нет, потому что на текущей стадии
Список легко получить поиском по всем файлам фрагмента "$hook = s2_hook(". Но он на самом деле не спасет от необходимости заглядывать в код, потому что нужно знать не только положение хуков, но и то, какие действия можно совершить в хуках, например, изменить переменную, вернуть результат из функции и т. д.
Я могу помочь на форуме с поиском нужных хуков по описанию функциональности расширения.