Settings API と仲良くなるライブラリー作ってみました

WordPress には色々便利なAPI が存在します。
Plugin API 、Shortcode APITheme Customization API など、WordPress の便利な機能を少ないコードで実装できるようになっています。

中でもセキュアな実装が求められるプラグイン管理メニューなどを追加することが出来る Settings API はとても重要な機能のひとつだと思います。

参考: Fumito Mizuno (@ounziw) さんのスライド


しかし、実際に管理画面を追加しようとすると 管理メニューの追加各種設定フィールドの追加、コールバック関数の実装など、そこそこ多くのコード記述が必要とされます。

管理メニューの追加と、Settings API による管理画面実装をもう少し簡単に使えるように出来ないかと思い、ちょっとしたプラグインを作ってみました。

mimosafa/wp-mimosafa-libs

プラグインをダウンロードしてアップロード、有効化してください。
なお、勢いで公開しているところもあるので、本番環境では使用は、もちろん、まだお勧めできません。vccw などで開発環境を作成してそちらで試してみてください。
また、PHP 5.4 以上で動くようになっています。

近いうちに公式レポジトリにアップできればな、と…

基本的な使い方

インスタンスの作成

mimosafa_settings_page_instance() 関数で返されるインスタンスを変数に代入してください

$page = mimosafa_settings_page_instance();

プラグインで使用される場合は、プラグインが全て読み込まれた後に実行されるように plugins_loaded アクションにフックしてください。

add_action( 'plugins_loaded', function() {
    if ( function_exists( 'mimosafa_settings_page_instance' ) {
        // ここに一連のコードを記述
    }
} );

ページの追加

クラスメソッドpage( string $page ) でトップメニューを追加します。

$page->page( 'top_level' )

一応これだけでメニューは追加されます。

スクリーンショット 2015-10-26 13.23.53

タイトル文字列の指定

第一引数 $page だけの指定では、その $page 文字列を適当にタイトルっぽくしたタイトルが表示されます。

$page->page( 'top_level', 'トップレベル', 'メニュータイトル' );

第二引数でタイトルを、第三引数でメニュータイトルを指定するか、

$page->page( 'top_level' );
$page->title( 'トップレベル' );
$page->menu_title( 'メニュータイトル' );

title( string $title ) メソッドでそのページのタイトル(title タグ、および h2 タグ)を、menu_title( string $menu_title ) メソッドでメニュータイトルを指定することができます。

スクリーンショット 2015-10-26 15.06.08

後述のsection()field() メソッドでも第二引数、およびtitle() メソッドで表示される見出し文字列を指定できます。

チェーンメソッド

ちなみに、各メソッドはチェーンメソッドで繋いでいけるので、

$page
->page( 'top_level' )
->title( 'トップレベル' )
->menu_title( 'メニュータイトル' );

と書くこともできます。(以下、チェーンメソッドで記載します)

サブメニューの追加

上記ではトップレベルから自前の管理メニューを作成していますが、既存のメニューにサブメニューとして管理メニューを追加する場合は、インスタンス作成後の最初の page() メソッドの引数を既存メニューのファイル名にしてください。

参照: Function Reference/add submenu page « WordPress Codex #Parameters

$page
->page( 'options-general.php' )
->page( 'submenu_page', 'サブメニュー' );

スクリーンショット 2015-10-26 15.08.31

トップレベルから自前の管理メニューにした場合でも、二つ目以降の page() からがサブメニュー扱いになります。

フォームの出力

  • page( string $page [, string $title = null [, string $menu_title = null ] ] )
  • section( string $id [, string $title = null ] )
  • field( string $id [, string $title = null ] )
  • option( string $option [, string|callable $callback = null [, callable $sanitize = null ] ])

を順繰りに使い、実際にフォームで管理したいオプションは option() の第一引数で指定してください。
page() の直後に option() を呼び出すことはできませんので、任意のセクションとフィールドを設定する必要があります。各関数の第一引数は(基本的に)固有のIDを指定してください。

また、content( string $text )description( string $text )、メソッドでテキスト、HTMLを追加できます。

実際にフォームを出力するまでの流れは下記コードのような感じです。

$page = mimosafa_settings_page_instance();
$page
->page( 'options-general.php' )
/**
 * サブメニューページ
 */
->page( 'submenu_page', 'サブメニュー' )
->description( 'サブメニューページの説明' )
/**
 * セクション
 */
->section( 'section_no_title' )
->description( 'セクションは必須です。第二引数で文字列を指定するとセクションタイトルが表示されます' )
->description( '<code>description()</code> は &amp;amp;lt;p&amp;amp;gt;タグで囲まれますが、' )
->content( '<code>content()</code> メソッドはそのまま出力されます。' )
->content( '<code>description()</code> と <code>content()</code> は' )
->content( '基本的にエスケープはしません。使用にはご注意ください。' )
/**
 * フィールド
 */
->field( 'text_fields', 'テキストフィールド' )
->description( 'フィールドも必須です' )
/**
 * オプション
 *
 * ->option( 'option_text' )
 * ->callback( 'text' )
 * ->sanitize( 'sanitize_title' )
 * と記述することもできます
 */
->option( 'option_text', 'text', 'sanitize_title' )
->description( '第二引数ではそのオプションをどんなフォームで表示するか指定します' )
->description( 'なにも指定しなかった場合は <code>input[type="text"]</code> になります。' )
->description( '第三引数ではサニタイズ関数を指定することができます' )
;

スクリーンショット 2015-10-26 16.11.48

callback() は、今のところ

  • text (Output: <input type="text">)
  • checkbox (Output: <input type="checkbox">)
  • textarea (Output: <textarea></textarea>)
  • select (Output: <select></select>)

が使用できます。
また、これらの文字列はそのままメソッド名としても使用でき、その場合はサニタイズ関数を引数としてとることができます。
なお、select の場合は、

  • item( string $value [, string $option_label ] )
  • items( array $items )

上記メソッドで選択肢を指定してあげる必要があります。

$page = mimosafa_settings_page_instance();
$page
->page( 'options-general.php' )
->page( 'submenu_page', 'サブメニュー' )
->description( 'サブメニューページの説明' )
->section( 'section_with_title', 'セクション' )
->field( 'checkbox_field', 'チェックボックス フィールド' )
/**
 * Checkbox
 */
->option( 'option_checkbox_normal' )
->callback( 'checkbox' );
/**
 * Sanitize 関数
 */
$checkboxSanitize = function( $var ) {
	return filter_var( $var, FILTER_VALIDATE_BOOLEAN );
};
$page
->sanitize( $checkboxSanitize )
/**
 * ラベル付き
 */
->option( 'option_checkbox_with_default_label', 'checkbox' )
->label( 'チェックボックスのラベル' ) // チェックボックスの場合、デフォルトでは右側にラベルがつく
->option( 'option_checkbox_with_left_label', 'checkbox' )
/**
 * label() メソッドの第二引数で指定できるのは、
 * - before
 * - after
 * - l (before のエイリアス)
 * - r (after のエイリアス)
 */
->label( 'デフォルトとは違う方向にもラベルをつけられます', 'before' )
->br()
->description( '<code>br()</code> メソッドでフォームの前に改行を入れることができます。' )
->field( 'textarea_and_select_field', 'テキストエリアとセレクトフィールド' )
/**
 * Textarea
 */
->option( 'option_textarea' )
->textarea( 'esc_html' )
->description( '定義済みの callback はそのままメソッド名とすることもできます。' )
->description( 'その場合、引数はサニタイズ関数をとることができます。' )
/**
 * Select
 */
->option( 'option_select_1' )
->select()
->item( 'item0', 'アイテム0' )
->item( 'item1', 'アイテム1' )
->item( 'item2', 'アイテム2' )
->item( 'item3', 'アイテム3' )
;

スクリーンショット 2015-10-26 18.31.01

$page = mimosafa_settings_page_instance();
$page
->page( 'options-general.php' )
->page( 'submenu_page', 'サブメニュー' )
->description( 'サブメニューページの説明' )
->section( 'section_with_title', 'セクション' )
->field( 'select_field' )
->title( 'セレクトフィールド' )
->option( 'option_select_1' )
;
$items = array(
	'array_member_0' => '選択肢0',
	'array_member_1' => '選択肢1',
	'array_member_2' => '選択肢2',
	'array_member_3' => '選択肢3',
	'array_member_4' => '選択肢4'
);
$selectFilter = function( $var ) use ( $items ) {
	return array_key_exists( $var, $items ) ? $var : null;
};
$page
->select()
->sanitize( $selectFilter )
->items( $items )
;

スクリーンショット 2015-10-26 17.45.13

と、まぁこんな感じです。

その他、実装済みの機能

トップレベルのページを定義した後に position( int $pos [, bool $replace_menu = false ] ) メソッドでメニュー内での位置を指定できます。第二引数を true にすると、既存のメニューを置き換えることもできます(が、メリットはないのであまり使わないでしょう)。

また、file( string $path [, array $file_include_args ] ) メソッドではページに自前のファイルをインクルードさせる事もできます。(ま、それが苦でないならそもそもこのライブラリーを必要とすることは無いと思いますが…)第二引数に連想配列を指定することで、その配列のキーを変数名とした変数をファイル内で使用することができます。

その他にもいくつか…

これらのメソッドは、wp-mimosafa-libs/page.php at master · mimosafa/wp-mimosafa-libs で定義されているので、興味があればご覧ください。

あと、Options API のインターフェースクラスも荒削りですが実装しており、特定のプレフィックスをつけたオプション群をあらかじめ定義することも出来るようになっています。
が、もうすこし詰めが必要かと思うのでまた今度…

今後

コードをチクチク書く只の WordPress オタクと化したぼくですが、皆さんからこのライブラリについてご意見いただけたりとか、プルリクいただけたりとかしたら、嬉しいです。
テストの書き方とか、Git のスマートな使い方とか勉強したいっっっす

^皿^;