Сегодня поговорим о плагинах. В комплекте MaxSite CMS (в версии 0.38 их 72 шт.). Если сравнивать с WordPress, то вроде бы немного, но проблема WordPress'а в том, что какие-то примитивные вещи требуется делать через «пень-колоду». В нем часто приходится идти на массу ухищрений, чтобы не только обойти ограничения системы, но и заставить корректно работать свои функции. Даже такое примитивное действие, как включение/инициализация плагина обросла неимоверными проверками и прочими никому не нужными глупостями. Я уже совсем молчу про полный хаос и отсутствие какой-то четкой структуры файлов плагинов. Они могут быть с любыми именами и любыми функциями.
При разработке MaxSite CMS я, естественно, учитывал все эти моменты, поэтому в системе используется строгое именование, а также предопределенный набор правил. Всё это позволяет значительно упростить создание плагина и сделать его многофункциональным. Именно поэтому я считаю, что по функциональности плагинов MaxSite CMS мало в чем уступает WordPress.
(Это старая статья, я её немного подправил под актуальное состояние.)
Каталог плагина
Любой плагин должен находиться в каталоге «maxsite/plugins» в собственном каталоге. Основной файл плагина - «index.php». После того, как плагин активирован система будет его подключать при инициализации обычным require_once().
Теоретически разработчик может прямо в файле без своих функций выполнить все нужные действия и именно так часто и происходит в WordPress. Но это будет очень плохим решением. Всё дело в том, что в WordPress'е не предусмотрено предопределеной функции «включения», поэтому разработчики просто вынуждены вешать различные хуки (ловушки, hooks, actions), чтобы подключить к системе свои функции.
Предопределенные функции
В MaxSite CMS всё продумано. При подключении плагина, система проверяет существование функции «плагин_autoload» и, если она есть, выполняет её. Именно в этой функции разработчик и должен цеплять хуки.
Кроме этого есть предопределенная функция, которая срабатывает при активации (включении) плагина: «плагин_activate», деактивации (отключении): «плагин_deactivate», а также деинсталяции (удалении): «плагин_uninstall».
Эти предопределенные функции позволяют охватить сразу весь цикл «жизни» плагина.
- При активации, плагин может создавать какие-то свои настройки, файлы, таблицы и т.п.
- При деинсталяции, все созданные настройки можно удалить, тем самым не оставляя в системе после себя «мусора».
- При деактивации можно не удалять настройки: при следующем включении плагина они сохранятся.
Готовые каркасы для плагинов
В комплекте MaxSite CMS уже поставляются готовые каркасы для плагинов и виджетов (виджеты отдельная тема). В каталоге plugins вы найдете такие файлы:
- info.php - информация о плагине.
- template for plugins.php - простой плагин без админки и виджетов.
- template for plugins-admin.php - плагин с админкой.
- template for widgets.php - плагин-виджет.
- template for plugins-all.php - плагин-виджет с админкой.
- admin.php - форма настройки.
Порядок такой. Вы делаете каталог плагина. В него копируете info.php и нужный «template for... .php». Переименовываете его в «index.php». Открываете файл и заменяете все «%%%» на имя каталога. Удалите строчку «%%% - замените на имя плагина». Всё, у вас рабочий плагин-каркас. Осталось в админ-панели активировать новый плагин и можно приступать к его программированию.
Предопределенные функции
Я еще раз обращу ваше внимание, на предопределенные функции. С одной стороны жесткая привязка имени плагина к каталогу не совсем гибкое решение, а с другой позволяет исключить вольности и сильно упростить организацию самого плагина и его взаимодействие с системой.
Таким образом, MaxSite CMS имеет довольно простой алгоритм работы с плагинами. Она смотрит основной каталог плагинов. После этого в каждом каталоге ищет файл index.php. Если такой файл есть, она подключает его обычным require(). После этого на основе имени каталога (по стути - это имя плагина) ищет предопределенные функции. Например для плагина demo при каждом подлючении будет выполняться функция demo_autoload().
Это очень простая и эффективная схема.
Хуки
В большинстве случаев плагины выводят результат либо по хукам, либо как виджеты.
Хуки нужно определять в функции _autoload плагина, например:
function demo_autoload($args = array()) { # к хуку content подключаем функцию demo_custom mso_hook_add( 'content', 'demo_custom'); } function demo_custom($text = '') { return $text . ' - DEMO - '; }
Хуков в системе определено довольно много, и нужно сразу учитывать какой должен быть аргумент у функций этого хука. Скажем для «content» - это строка, а у «pagination» - массив пагинации. Все функции хуков должны возвращать результат по return, чтобы обеспечить дальнейшую цепочку выполнения.
Функция mso_hook_add() не выполняет никаких действий, кроме как добавляет в специальный массив указанную функцию. Сама же функция срабатывает когда будет вызван указанных хук (mso_hook()). Например «content» срабатывает при получении текста страницы из базы данных. Соответственно в этот момент и сработает наша demo_custom().
Опции плагина
Плагин может использовать свои опции. Существует два вида: обычные и float-опции. Первые хранят значения в базе данных. Вторые - в виде файлов. Какие из них выбрать решает разработчик. Я предпочитаю следовать простому правилу: если опция небольшая, то это обычная опция. Если большой текст, то это float.
Сами по себе опции кэшируются, поэтому до определенного размера нет надобности заботиться об их оптимизации. Но когда хранимые значения могут быть большими, то есть смысл вынести их отдельно. Тогда float-опции очень даже пригодятся. Скажем плагин «Ушки» как раз сделан как float-опции.
Опции для плагина удобно хранить в виде массива ключ => значение. Для записи опций существует функция mso_add_option(), например:
$options = array(); $options['header'] = 'Заголовок'; $options['text'] = 'Текст'; mso_add_option('demo', $options, 'plugins');
Первый параметр - это ключ опции. Второй - массив опций (можно использовать не только массив, а любой другой тип, например строку). Третий - «тип» опции: дело в том, что «тип» позволяет группировать опции и делать их уникальными. Например для основных настроек сайта используется тип «general». Если наша опция (ключ) совпадет с тем, что определено системой, то опция окажется переписанной. Чтобы это исключить и используется тип. Причем тип вы можете использовать и свой. Например вы не хотите использовать массив для хранения всех опций. И хотя такое желание я и расценю более чем странным, MaxSite CMS преспокойно выполнит ваш «заказ»:
mso_add_option('header', 'Заголовок', 'demo'); mso_add_option('text', 'Какой-то текст', 'demo'); mso_add_option('autor', 'Автор', 'demo');
То есть мы просто определили тип «demo».
Для получения опций служит функция mso_get_option():
$options = mso_get_option('demo', 'plugins', array()); if ( !isset($options['header']) ) $options['header'] = 'Заголовок'; if ( !isset($options['text']) ) $options['text'] = 'Текст по-умолчанию';
Первый параметр - ключ, второй - тип, третий - значение, если опции нет. После получения опции всегда есть смысл проверить существование в массиве нужных нам ключей.
Функция mso_admin_plugin_options()
Опции - хорошая возможность сделать плагин более функциональным. Когда я в начале этой статьи говорил о том, что плагины MaxSite CMS с лихвой перекрывают плагины WordPress, то имел ввиду именно тот факт, что плагины MaxSite CMS имеют куда больше настроек, чем аналогичные в WordPress. Подключить свои опции к WordPress - целое дело. Это нужно делать отдельную страницу, четкого и простого руководства нет, поэтому разработчики лепят что-ни-поподя.
В MaxSite CMS меня такое положение дел не устараивало изначально, поэтому я сразу же предложил несколько вариантов использования опций для плагинов. Один из вариантов мы уже рассмотрели. Для его реализации следует подключить файл admin.php, в котором происходит вывод настроек плагина, обработка входящих данных и т.д.
Со временем я понял, что в принципе большинство настроек плагинов имеют единый алгоритм и схему работы, с разницей в виде текстов и названий опций. Тут-то и пришла идея кардинально изменить подход - задавать все настройки в виде одной-единственной функции, которая сама будет:
- отображать форму
- принимать данные
- отображать сопутствующие тексты.
Таким образом появилась фунция mso_admin_plugin_options(). Вот так она объявлена:
mso_admin_plugin_options( $key, $type, $ar, $title = '', $info = '', $text_other = true)
Параметры $key и $type - название опции. Тут такой нюанс. Функция будет работать только с опциями в виде массива. То есть для плагина создается одна опция, которая представляет собой обычный массив, где ключи массива - прочие настройки (как в вышеприведенном примере - $options).
Третий параметр $ar - массив, где нужно описать ключи массива опций (пример будет чуть ниже).
Параметр $title и $info задают тексты, которые выводятся до формы. Параметр $text_other указывает выводить ли строчку со ссылками на страницу плагинов или можно задать свой произвольный вариант.
Подключение mso_admin_plugin_options()
Функцию mso_admin_plugin_options() можно использовать по своим задачам, но для полной интеграции с плагинами есть предопределенная функция плагин_mso_options. Если её объявить в плагине, то MaxSite CMS автоматически её выполнит. Таким образом именно в ней и следует располагать mso_admin_plugin_options().
Ниже я привожу пример реализации для плагина demo.
function demo_mso_options() { mso_admin_plugin_options('plugin_demo', 'plugins', # ключ, тип # ключи массива опций с их описанием array( 'f1' => array( 'type' => 'text', # тип поля в форме 'name' => 'название', 'description' => 'описание', 'default' => '' # значение по-умолчанию ), 'f2' => array( 'type' => 'textarea', 'name' => 'название', 'description' => 'описание', 'default' => '' ), 'f3' => array( 'type' => 'checkbox', 'name' => 'название', 'description' => 'описание', 'default' => '1' // для чекбоксов только 1 и 0 ), 'f4' => array( 'type' => 'select', 'name' => 'название', 'description' => 'описание', 'values' => '0.00||Гринвич (0) # 1.00||что-то # 2.00||Киев (+2) # 3.00||Москва (+3)', // правила для select как в ini-файлах 'default' => '2.00' ), ), 'Настройки плагина demo', // титул 'Укажите необходимые опции.' // инфо ); }
Таким образом, если нужно сделать плагин и ввести в него произвольное количество опций, то самым простым и эффективным способом будет воспользовать предопределенной функцией mso_admin_plugin_options(), которая возьмет на себя всю черновую работу.
Подключения к админ-панели. Разрешения
Любой плагин элементарно подключается к админ-панели. Для этого потребуется всего несколько строчек кода и мы их сейчас рассмотрим. Но перед этим следует сразу определиться с разрешениями пользователей.
В MaxSite CMS можно создать разрешение для доступа. Делается это одной строчкой:
mso_create_allow('demo_edit', 'Админ-доступ к DEMO');
Этот код следует размещать либо в «_autoload», либо в «_activate» плагина. После того, как разрешение (в нашем случае это «demo_edit») создано, то система добавляет его в базу данных. Выставить же «галочки» для каждой группы пользователей можно уже в админ-панели.
Если разрешение больше не нужно, то оно удаляется с помощью mso_remove_allow():
mso_remove_allow('demo_edit');
Логичней всего, конечно сделать это в «_uninstall».
Теперь, для того, чтобы включить проверку разрешения в наши функции, используется такой код:
if ( !mso_check_allow('demo_edit') ) { echo 'Доступ запрещен'; return; }
Всегда ли нужно использовать разрешения? Тут решает разработчик. Обычно, для тех плагинов, которые имеют свои страницы настроек в админ-панели лучше делать такие разрешения. Причем, замечу, что речь идет только о доступе к странице настроек плагина.
Подключения к админ-панели. Общая схема
Общий алгоритм подключения к админ-панели таков:
- В «_autoload» создаем разрешение.
- Функция указанная в хуке «admin_init» выполняется при входе в админ-панель (срабатывает хук - выполняется функция). В ней мы проверяем разрешение. Если его нет (false), то выходим.
- Если разрешение есть, то функцией «mso_admin_menu_add()» добавляем пункт в меню админки.
- Функцией «mso_admin_url_hook()» цепляем специальный хук (по имени плагина), который будет срабатывать при переходе к нашей странице настройки, на указанную нами функцию.
- В указанной нами функции проверяем разрешение. Динамически создаем функцию для хуков «mso_admin_header» и «admin_title», для красивого вывода титлов в админке. После этого переходим к отдельному файлу admin.php (имя произвольно).
- В файле admin.php располагаем форму и код, с помощью которых и выполняются настройки плагина.
- Если же мы используем mso_admin_plugin_options(), то проверяем разрешения перед этой функцией.
Подключения к админ-панели. Код
Дабы всё стало понятно, привожу полный код-каркас:
# функция автоподключения плагина function demo_autoload($args = array()) { mso_create_allow('demo_edit', 'Админ-доступ к DEMO'); mso_hook_add( 'admin_init', 'demo_admin_init'); # хук на админку } # функция выполняется при хуке admin_init function demo_admin_init($args = array()) { if ( !mso_check_allow('demo_edit') ) return $args; # добавляем свой пункт в меню админки # первый параметр - группа в меню # второй - это действие/адрес в url - http://сайт/admin/demo # Третий - название ссылки mso_admin_menu_add('plugins', 'demo', 'Плагин DEMO'); # прописываем для указаного url # связанную функцию именно она будет вызываться, когда # будет идти обращение по адресу http://сайт/admin/demo mso_admin_url_hook ('demo', 'demo_admin_page'); return $args; } # функция вызываемая при хуке, указанном в mso_admin_url_hook function demo_admin_page($args = array()) { if ( !mso_check_allow('demo_edit') ) { echo 'Доступ запрещен'; return $args; } mso_hook_add_dinamic( 'mso_admin_header', ' return $args . "DEMO"; ' ); mso_hook_add_dinamic( 'admin_title', ' return "DEMO - " . $args; ' ); # выносим админские функции отдельно в файл require(getinfo('plugins_dir') . 'demo/admin.php'); }
Форма настроек плагина
В этом примере сама страница, где выводится форма с настройками, вынесена в отдельный файл admin.php. Сделано это из-за удобства: форма это HTML и было бы удобней работать с отдельным файлом.
Отдельную страницу настроек плагина обычно делают, когда плагин выполняет какое-то особое действие. Например в ушках - свой вывод, своя обработка данных.
Сама работа с формой уже миллион раз описана во всех учебниках по PHP. Алгоритм до безобразия простой:
- Проверяем есть ли отправка POST.
- Если есть, то обновляем опции или выполняем нужное действие.
- Выводим форму.
Естественно, и форма, и опции у каждого плагина будут разными. Поэтому здесь я лишь обрисую общие моменты.
Получение данных выполняется с помощью блока:
if ( $post = mso_check_post(array('f_session_id', 'f_submit', 'f_header')) ) { mso_checkreferer(); $options = array(); $options['header'] = $post['f_header']; mso_add_option('demo', $options, 'plugins'); echo '<div class="update">Обновлено!</div>'; }
Функция mso_check_post() проверяет $_POST на указанные имена полей. То есть, когда мы отправили форму. Скрытое поле «f_session_id» используется для проверки текущей сессии (если нужно). Таким образом, если форма не будет содержать этих полей, функция вернет false и условие выполнено не будет.
Функция mso_checkreferer() проверяет откуда отправлены данные. Естественно, что они должны быть отправлены с этой же страницы. Это позволяет отчасти защититься от различного рода хакерских атак.
Следующий код вам уже знаком. Мы формируем массив опций и сохраняем его. В завершении выдаем уведомление, что данные обновлены.
Следующий блок - получение опций перед формой с помощью mso_get_option(). Код приведен в начале статьи, поэтому повторяться не будем.
Последний блок - вывод самой формы.
$form = '<h2>Настройки</h2>'; $form .= '<p><strong>Заголовок:</strong> ' . ' <input name="f_header" type="text" value="' . $options['header'] . '"></p>'; echo '<form action="" method="post">' . mso_form_session('f_session_id'); echo $form; echo '<input type="submit" name="f_submit" value="Сохранить изменения">'; echo '</form>';
Как видите форма очень простая: создаем нужные нам поля (input), и выводим в браузер. Функция mso_form_session() используется для формирования скрытого (hidden) поля input, в котором хранится текущая сессия. Во всём остальном - обычная html-форма.
Вопросы безопасности
MaxSite CMS устроена так, что «голое» ядро содержит лишь самый минимум, а все «внешние коммуникации» осуществляются через плагины. Даже админ-панель - это самые обычные плагины. Поэтому при разработке плагинов, особено тех, которые делают доступ к админ-панели и выводят свои результаты на страницах сайта, следует с особой тщательностью подходить к вопросам безопасности.
Отмечу несколько моментов.
- Всегда проверяйте опции на нужный тип. Прежде всего это относится к integer. Привести строку к числу в PHP очень просто, а вам потом не нужно вылавливать неверно введенные данные.
- Всегда проверяйте существование ключа опции с помощью isset(). Если его нет, выставляйте дефолтное значение.
- При выводе текста на страницах используйте htmlspecialchars() и strip_tags(). Особенно там, где html-тэги не нужны по определению.
- Никогда не принимайте GET-параметры. В MaxSite CMS принято все данные отправлять только через POST. POST также можно подделать, но невозможно передать по ссылке. Поэтому часть угроз автоматически снимается.
Отдельным пунктом хочу выделить XSS-атаки. Злоумышленик может оставить вредоносный код в тексте. Если такой текст отображается на сайте, то атаке окажутся подвержены посетители. Если же текст отображается в админ-панели, то данные админа могут «утечь» злоумышленнику.
Для того, чтобы обнаружить XSS-атаку можно воспользоваться средствами CodeIgniter.
$CI = & get_instance(); $text_in = 'какой-то входящий текст, который нужно проверить'; // выполняем XSS-фильтрацию $text_xss = $CI->input->xss_clean($text_in); // если тексты не равны, значит существует опасность XSS-атаки if ($text_in != $text_xss) { echo '<div class="error">Внимание! Обнаружена XSS-атака!</div>'; // дальше решаем что делать }
Если же нужно просто вывести текст, то можно сразу удалять возможные XSS:
$CI = & get_instance(); $text = 'какой-то входящий текст, который нужно проверить'; $text = $CI->input->xss_clean($text);
С помощью таких несложных манипуляций можно избежать одной из самых распространенных опасностей.
Комментариев: 6 RSS
1Аноним11-03-2009 00:37
Типы опций бывают general, plugins и?
Почему странным? У меня, например, есть для нескольких страниц (из сотен на всём сайте) настройка отображения\неотображения некоторых данных. Я для этого выбрал тип опций plugins, но в основном потому, что делал по образу и подобию найденного в официальном коде. Это как бы не general, потому что не для всего сайта настраивается, а для каждого случая по отдельности. Но и plugins назвать это сложно. Если б я тогда эту лекцию прочёл — назвал бы как-то типа spesial или даже my_spesial_options_for_this_site. А назвал plugins — админ-плугином переключаю.
2Аноним11-03-2009 09:22
Оч. обширный разбор полета плагинов. Благодарю.
Один вопрос - что такое "хук"(ловушка)? Растолкуйте непрограммисту.
3Аноним11-03-2009 11:05
Странным, потому что опции лучше хранить в виде единого массива. То есть тип и ключ может быть как душе угодно, а вот саму опцию лучше делать в виде массива. Всё из-за того, что добавление опции это +1 SQL-запрос.
Хук - это что-то вроде «действия». Например, я предусмотрел хук «body_end». Если кто-то придумает функцию, которая должна срабатывать в месте «body_end», то он её «цепляет» к этому хуку.
4Евгений Самборский12-03-2009 14:54
Плагины приятней разрабатывать чем в том же вордпрессе.
p.s. от функции mso_checkreferer я отказался, она больше вредит, если чел на опере сидит.
5truefinger20-03-2009 06:32
существует ли плагин для создания мультиязычного сайта?
6Irisha26-05-2011 11:28
У меня возникла проблема с добавление плагина в каталог «maxsite/plugins». Добавляла через панель ISP Manager, предоставленную хостингом, все время пишет ошибка 404 и куча иероглифов. Как еще можно его туда загрузить?