Пишем плагин для WordPress: часть 4, настройки нашего плагина
В этой статье опишу обеспечение возможности изменения параметров плагина блоггерами.
Пришло время предоставить возможность блоггеру просмотреть / изменить параметры нашего плагина в консоли управления wordpress. Сейчас мы пишем не виджет, поэтому о интерфейсе виджетов ничего и не скажу.
Обеспечим возможность настройки нашего плагина через меню Параметры в консоли WordPress (рецепт в кодексе: Creating Options Pages):
register_activation_hook ( __FILE__, array('cos_search_provider', 'activation' )); register_deactivation_hook ( __FILE__, array('cos_search_provider', 'deactivation' )); register_uninstall_hook ( __FILE__, array('cos_search_provider', 'uninstall' )); add_action('plugins_loaded', array('cos_search_provider', 'plugins_loaded')); class cos_search_provider { private static $_name; private static $_namespace = __CLASS__; private static $_folder; private static $_domain; private static $_path; private static $_link = 'http://sergey-s-betke.blogs.novgaro.ru/ie-search-provider-wordpress-plugin/'; private static $options; public static function activation() { } public static function deactivation() { unregister_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); } public static function uninstall() { } public static function plugins_loaded() { self::$_folder = dirname(plugin_basename(__FILE__)); self::$_domain = self::$_folder; self::$_path = WP_PLUGIN_DIR . '/' . self::$_folder . '/'; load_plugin_textdomain(self::$_domain, false, self::$_folder . '/languages/'); self::$_name = __('COS Search provider', self::$_domain); add_action('admin_init', array(__CLASS__, 'admin_init')); add_action('admin_menu', array(__CLASS__, 'admin_menu')); } public static function admin_init() { register_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); add_settings_section( self::$_namespace . '_main_options', __('Main Settings', self::$_domain), false, self::$_namespace . '_options_page' ); add_settings_field( self::$_namespace . '_options[title]', __('Title', self::$_domain), array(__CLASS__, 'option_control_title'), self::$_namespace . '_options_page', self::$_namespace . '_main_options' ); } public static function option_control_title() { ?> <input name="<?php echo self::$_namespace . '_options[title]' ?>" type="text" size="50" value="<?php echo self::$options['title']; ?>" /> <?php } public static function validate_options($options) { if (!is_array($options)) { $options = array( 'title' => get_bloginfo('name') ); } if ($options['title'] == '') $options['title'] = get_bloginfo('name'); return $options; } public static function admin_menu() { add_options_page( __('COS search provider options', self::$_domain) , self::$_name , 'manage_options' , self::$_namespace . '_options_page' , array(__CLASS__, 'options_page') ); } public static function options_page() { ?> <div class="wrap"> <?php screen_icon('options-general'); ?> <h2><?php _e('COS search provider options', self::$_domain) ?></h2> <form method="post" action="options.php"> <?php settings_fields(self::$_namespace); self::$options = self::validate_options(get_option(self::$_namespace . '_options')); do_settings_sections(self::$_namespace . '_options_page'); ?> <p class="submit"> <input name="Submit" type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" /> </p> </form> </div> <?php } }
Каким же образом мы реализуем необходимый интерфейс? По шагам.
Инициализация консоли
Вешаем обработчик (hook) на событие (ну – мне так удобнее выражаться) admin-init. Данное событие возникает только при инициализации консоли, при просмотре блога его не будет. И данное событие возникает ранее любых других событий консоли.
... add_action('admin_init', array(__CLASS__, 'admin_init')); ... public static function admin_init() { ... }
Регистрируем параметры плагина
Настоятельно рекомендую предварительно изучить Settings API. Оно доступно начиная с версии 2.7 wordpress. Итак, в обработчике admin-init регистрируем параметры нашего плагина через вызов API register_setting.
... add_action('admin_init', array(__CLASS__, 'admin_init')); ... public static function admin_init() { register_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); add_settings_section( self::$_namespace . '_main_options', __('Main Settings', self::$_domain), false, self::$_namespace . '_options_page' ); add_settings_field( self::$_namespace . '_options[title]', __('Title', self::$_domain), array(__CLASS__, 'option_control_title'), self::$_namespace . '_options_page', self::$_namespace . '_main_options' ); }
Могут возникнуть некоторые иллюзии. Первым параметром этой функции является идентификатор группы параметров (именно для этих целей мы и используем наше поле $_namespace). И возможная иллюзия – идентификатор опции (второй параметр функции) должен быть уникальным только в пределах группы. ЭТО НЕ ТАК! Идентификатор опции должен быть уникальным и за пределами группы опций. Именно поэтому мы и добавляем к нему self::$_namespace в качестве префикса.
Опять-таки, возможны разные подходы к сохранению параметров. Я выбрал путь сохранения всех параметров в одном массиве и в одном параметре с точки зрения базы данных. Но в этом случае редактирование параметров моего плагина будет возможно только через интерфейс моего же плагина. Если же сохранять каждый параметр отдельно – будет возможность просмотра и редактирования параметров без собственного интерфейса плагина, но потребуется больше обращений к БД, в том числе уже и при “обычных” запросах к блогу, а не к консоли, что не лучшим образом скажется на производительности. Выбор за Вами.
Последним параметром функции register_setting идёт callback (опционально), позволяющий проконтролировать и исправить параметры перед сохранением. В моём случае проверяю: если параметров не было до сих пор – создаю массив, если есть, но title – пустая строка, получаю наименование блога и присваиваю опции title.
Кроме определения опций для базы данных мы должны определить и интерфейс для их просмотра и редактирования. Создаём в интерфейсе секцию для редактирования наших опций – add_settings_section, add_settings_field. Последняя должна быть вызвана для каждой опции, которую мы хотим редактировать, первая – для каждой секции опций (если мы их планируем несколько). Третий параметр при регистрации секции – callback, который выдаст в поток (а не вернёт в качестве результата) html код, поясняющий назначение секции опций. Мне это не требуется, поэтому опустил этот параметр. 4ый параметр в обоих случаях – идентификатор (уникальный опять-таки) страницы опций, на которой мы и “регистрируем” таким образом свои “опции”. Третим параметров в add_settings_field мы передаём callback, который опять-таки выдаст в поток html код, обеспечивающий просмотр и редактирование опции, в нашем случае – self::$options[title].
public static function option_control_title() { ?> <input name="<?php echo self::$_namespace . '_options[title]' ?>" type="text" size="50" value="<?php echo self::$options['title']; ?>" /> <?php }
Как видно, мы не думаем о именовании опции в диалоге (наименование уже передали вторым параметром в add_settings_field), только сам control формируем. Важные моменты здесь следующие:
- атрибут name тега input должен иметь вид <идентификатор опции – второй параметр register_setting>[<идентификатор элемента массива, если используем массив опций>]. Если Вы не выполните указанное требование, API settings_fields не сможет сохранить изменения опций.
- Вы можете использовать одну функцию для генерации управляющих элементов для ряда Ваших опций, передавая дополнительные параметры через последний неиспользованный в моём примере аргумент add_settings_field (он должен быть массивом). Вероятнее всего, в дальнейшем я воспользуюсь подобным вариантом.
- если Вы сохраняете Ваши опции отдельно (регистрировали каждую опцию отдельно через register_setting), тогда Вам следует добавить чтение (get_option) в код функции выше (option_control_title) перед формирование html кода, например:
public static function option_control_title() { $title = get_option(self::$_namespace . '_title'); ?> <input name="<?php echo self::$_namespace . '_title' ?>" type="text" size="50" value="<?php echo $title; ?>" /> <?php }
- если Вы сохраняете все Ваши опции в одном массиве (как это делаю я) тогда многократно (для каждой опции) читать из БД массив глупо. Поэтому чтение массива опций переносим в метод генерации формы, следом за settings_fields (об этом далее читайте), вводим соответствующее поле в класс, и в методах генерации элементов отображения и редактирования опций используем поле класса (в моём случае — $options).
P.S. Не забываем обеспечить и удаление опций плагина при его деактивации, если это необходимо:
... register_deactivation_hook ( __FILE__, array('cos_search_provider', 'deactivation' )); register_uninstall_hook ( __FILE__, array('cos_search_provider', 'uninstall' )); ... public static function uninstall() { delete_option(self::$_namespace . '_options'); } public static function deactivation() { unregister_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); }
На практике многие разработчики добавляют в свои опции 2 “особых” опции: удалять опции при деактивации плагина, удалять их при удалении плагина. Если опций много, практика может оказать очень даже полезной. В дальнейшем планирую её поддержать.
Расширяем меню консоли
Зарегистрируем собственную страницу в меню консоли. Для чего повесим обработчик на событие admin_menu.
... add_action('admin_menu', array(__CLASS__, 'admin_menu')); ... public static function admin_menu() { add_options_page( __('COS search provider options', self::$_domain) , self::$_name , 'manage_options' , self::$_namespace , array(__CLASS__, 'options_page') ); }
Для регистрации своего раздела консоли служит группа функций add_menu_page, и одна из специализаций этой функции – add_options_page, которую мы и используем. Свою страницу настройки мы можем интегрировать практически в любое меню консоли, для регистрации в меню Настройки служит как раз add_options_page. Благодаря поддержке ролей в wordpress, теперь при регистрации своих диалогов в меню консоли мы можем указать привилегии (manage_options в моём примере и в большинстве случае), которыми должен обладать пользователь, чтобы получить доступ к нашим опциям. То есть мы указываем не роли даже, а привилегии. Пользователь, не обладающий достаточными привилегиями, просто не увидит нашей страницы в меню консоли.
Реализуем страницу параметров плагина
Нашёл рецепт. Теперь, собственно говоря, сама страница опций плагина.
... register_setting( self::$_namespace, self::$_namespace . '_options', array(__CLASS__, 'validate_options') ); ... ... add_options_page( __('COS search provider options', self::$_domain) , self::$_name , 'manage_options' , self::$_namespace , array(__CLASS__, 'options_page') ); ... public static function options_page() { ?> <div class="wrap"> <?php screen_icon('options-general'); ?> <h2><?php _e('COS search provider options', self::$_domain) ?></h2> <form method="post" action="options.php"> <?php settings_fields(self::$_namespace); ?> <?php $options = self::validate_options(get_option(self::$_namespace . '_options')); ?> <table class="form-table"> <tr valign="top"> <th scope="row"><?php _e('Title', self::$_domain); ?></th> <td> <input name="<?php echo self::$_namespace . '_options[title]' ?>" type="text" size="50" value="<?php echo $options['title']; ?>" /> </td> </tr> </table> <p class="submit"> <input name="Submit" type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" /> </p> </form> </div> <?php }
Зарегистрированная нами через add_options_page функция options_page должна сгенерировать html код самой страницы опций плагина. В принципе, никто не мешаем нам всё сделать здесь “руками”, и разместить произвольный html код, как угодно оформленный, вставить iframe и так далее. Но если мы преследуем только одну цель – дать пользователю возможность просмотреть и изменить параметры плагина, мы можем воспользоваться API wordpress и упростить нашу задачу.
Обёртка в виде тегов div, h2, form, а также p и input в конце практически обязательны. А вот далее уже возможны варианты.
Иконку для нашей страницы опций возьмём “стандартную”, и получим её через screen_icon.
Сразу после открытия формы тегом form используем API settings_fields. Именно здесь (судя по всему, только здесь) нам и потребуется идентификатор группы опций, указанный нами ранее в register_setting. API settings_fields сгенерирует весь необходимый код для обработки метода HTTP POST в нашей форме, то есть код, проверяющий (с помощью зарегистрированного нами валидатора validate_options) изменённые пользователем опции, корректирующий их и сохраняющий в БД. Достаточно приятная инкапсуляция wordpress.
Следующим шагом мы загружаем опции из БД (get_option). Следует обратить внимание на её второй параметр. Если Вы сохраняете опции не массивом, а по одной, то Вы можете значения по умолчанию задавать не в валидаторе, а непосредственно при вызове get_option.
Далее я пока что оставил часть кода – “ручное” формирование html кода страницы опций. Чтобы Ваша страница не выглядела с точки зрения дизайна белой вороной в консоли wordpress, следует пользоваться таблицей (смотрите Creating Options Pages), как и показано выше. Важные моменты здесь следующие:
- атрибут name тега input должен иметь вид <идентификатор опции – второй параметр register_setting>[<идентификатор элемента массива, если используем массив опций>]. Если Вы не выполните указанное требование, API settings_fields не сможет сохранить изменения.
Однако, согласитесь, подобная генерация html кода страницы опций противоречит принципам инкапсуляции. Посему продолжим изучение API wordpress, чтобы исключить и эти анахронизмы (которые являлись единственным решением до wordpress 2.7, если не ошибаюсь).
Так выглядел бы наш код без использования add_settings_section, add_settings_field. А до wordpress 2.7 он выглядел бы ещё более жутко – никакой инкапсуляции. Теперь же мы можем попробовать его переписать с большей поддержкой инкапсуляции от wordpress 2.7:
public static function options_page() { ?> <div class="wrap"> <?php screen_icon('options-general'); ?> <h2><?php _e('COS search provider options', self::$_domain) ?></h2> <form method="post" action="options.php"> <?php settings_fields(self::$_namespace); self::$options = self::validate_options(get_option(self::$_namespace . '_options')); do_settings_sections(self::$_namespace . '_options_page'); ?> <p class="submit"> <input name="Submit" type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" /> </p> </form> </div> <?php }
Как видно, генерацию таблицы мы теперь выбросили полностью, заменив её на вызов API do_settings_sections. И в качестве параметра мы передаём ей тот же идентификатор страницы опций, что и передавали в add_settings_section, add_settings_field.
P.S. Важный момент: таким образом (через add_settings_section, add_settings_field) мы можем добавить секции и опции (или опции в существующие секции) и на другие страницы опций (general, reading, writing и так далее). В дальнейшем я планирую добавить ряд опций на страницу Чтение (reading).
Предварительный итог
После проделанной работы мы уже видим страницу опций своего плагина в меню Параметры. И не только. Приведённый код уже предоставляет и сам интерфейс изменения параметров плагина (рисунок слева), и позволяет изменять и сохранять их.
В следующей статье я приведу код плагина целиком (созданный до этого этапа включительно), приступим к реализации функционала плагина как такового.
Дополнительная информация:
Я думаю, учитывая столь позднюю инкапсуляцию механизма опций в консоли wordpress (аж с версии 2.7), очевидна и потребность (спрос), и предложение всевозможных фреймворков, закрывающих этот пробел wordpress (до версии 2.7). Приведу ссылки на некоторые из них:
- WordPress Plugin Framework. Огорчает дата последнего обновления – 06.07.2008, при этом информации о совместимости с WordPress 3.0 нет. Существенный недостаток. И этот продукт доступен на code.google.com, однако массовым его не назовёшь. И активности нет.
- Simple WordPress Framework. Этот framework уже обновлялся позднее.
- YD WordPress Plugins Framework. Релиз только один, но свежий и тестировался под WordPress 3.0.3.
- и далее – множество http://wordpress.org/extend/plugins/search.php?q=framework.
RSS комментарии
Обратная ссылка