Статья размещена автором Бетке Сергей Сергеевич

Пишем плагин для 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: часть 4, настройки нашего плагина Пишем плагин для WordPress: часть 4, настройки нашего плагина После проделанной работы мы уже видим страницу опций своего плагина в меню Параметры. И не только. Приведённый код уже предоставляет и сам интерфейс изменения параметров плагина (рисунок слева), и позволяет изменять и сохранять их.

В следующей статье я приведу код плагина целиком (созданный до этого этапа включительно), приступим к реализации функционала плагина как такового.

Дополнительная информация:

Я думаю, учитывая столь позднюю инкапсуляцию механизма опций в консоли wordpress (аж с версии 2.7), очевидна и потребность (спрос), и предложение всевозможных фреймворков, закрывающих этот пробел wordpress (до версии 2.7). Приведу ссылки на некоторые из них:

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка