WordPress メニュー・管理画面・オプション・設定等のメモ。

# WordPressの管理画面、メニュー、フォームやオプションのメモ

前回の続きです。

https://qiita.com/diconran/items/e4dc2d6f0a3638e0b8da

今回Ajaxも含めて書くつもりでしたが、あまりにも量が多いので分割することにしました。
意識がもうろうとして自分で何を書いてるかわかりませんが、コードから読み取ってくださいー。

あ、基本セキュリティーとか考慮してません。
登場する関数は適当にググってください。

そのへんはご自身で対応してください。


## メニュー


```php
add_action('admin_menu', function(){
    add_menu_page(
        'だいこんらん！',
        '大根乱設定',
        'manage_options',
        'diconran-config',
        function(){
            echo '<h2>大根乱の設定だどー</h2>';
        }
    );
});
```

![wp-menu-1.png](https://qiita-image-store.s3.amazonaws.com/0/205767/d90398b2-9239-8538-645a-d096af14544a.png)


add_menu_page()はadmin_menuアクションで行います。

第一引数がタイトル、第二がメニューの項目名、第三が前回紹介した権限(manage_optionsは管理者のみメニューが表示され、アクセス出来る)。
そして第四がこのメニューのスラグ(識別IDのようなもの)を指定します。
表示したい内容は第五のコールバック関数でechoします。

本当は第六のアイコンや第七の表示順序などもありますが面倒なので省略してます。
コールバックをから文字にしてスラグにファイル名を指定したりできますがそれも省略します。

ここでは重要ではないので。



### サブメニュー

メニューの下に、サブメニュー２つをぶら下げる例です。

```php
add_action('admin_menu', function(){

    add_menu_page(
        'だいこんらん！',
        '大根乱設定',
        'manage_options',
        'diconran-config', //親スラグ
        function(){
            echo '<h2>大根乱の設定だどー</h2>';
        }
    );


    add_submenu_page(
        'diconran-config', //親スラグを指定
        'ぷちこんらん１！',
        'ちょっと混乱１',
        'manage_options',
        'diconran-sub_1',
        function(){
            echo '<h2>めだぱにぃ〜！</h2>';
        }
    );

    add_submenu_page(
        'diconran-config', //親スラグを指定
        'ぷちこんらん２！',
        'ちょっと混乱２',
        'manage_options',
        'diconran-sub_2',
        function(){
            echo '<h2>めだぱ２ぃ〜！</h2>';
        }
    );
});
```

![wp-sub-menu.png](https://qiita-image-store.s3.amazonaws.com/0/205767/2cb770a6-0beb-4dd9-f640-e5319f093c62.png)

![wp-sub-menu-2.png](https://qiita-image-store.s3.amazonaws.com/0/205767/95f12d73-9b6e-1360-6284-200bbe9fbc29.png)


add_submenu_page()の第一引数には、add_menu_page()のスラグを渡します。
それ以降はadd_menu_page()と同じ。


### 元々あるメニューにぶら下がる


```php
add_action('admin_menu', function(){

    add_options_page(
        'だいこんらん！',
        '大根乱設定',
        'manage_options',
        'diconran-config',
        function(){
            echo '<h2>大根乱の設定だどー</h2>';
        }
    );
});
```

![wp-options-sub-menu.png](https://qiita-image-store.s3.amazonaws.com/0/205767/8294c439-365e-f469-cee0-4c9391299a69.png)




add_options_page()は、ワードプレスに元々ある「設定」メニューにぶら下がります。
他に、テーマにぶら下がるならadd_theme_page()が、メディアにぶら下がるならadd_media_page()を使うと便利です。


```php
function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
	return add_submenu_page( 'options-general.php', $page_title, $menu_title, $capability, $menu_slug, $function );
}

function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
	return add_submenu_page( 'themes.php', $page_title, $menu_title, $capability, $menu_slug, $function );
}
function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
	return add_submenu_page( 'upload.php', $page_title, $menu_title, $capability, $menu_slug, $function );
}
```

内部ではこんな感じで定義してあります。




## オプション

![wp_options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/a8c4b97c-e1e3-91e2-4dd0-7ae5a1010e5f.png)


wp_optionsテーブルにワードプレスの設定用のデータが入ってます。
Key-Valueのようなテーブルで、get_option()で取得したり、update_option()で更新したりします。
wp_optionsはオプション名とオプション値が対になってます。

例えば、ワードプレスの名前はblognameで取得したりします。
テーマのfunctions.phpにこんな感じで記述して、


```php
$bn = get_option('blogname');
update_option('blogname', $bn . 'だよ〜ん♪');
```

ブラウザを何回か更新すると、

![wp2-wp_options2.png](https://qiita-image-store.s3.amazonaws.com/0/205767/b7621d41-6b41-1654-d0eb-650563414b80.png)


wp_optionsのテーブルに反映されます。


![wp2-update_option.png](https://qiita-image-store.s3.amazonaws.com/0/205767/e5a54722-2955-9817-9868-d7c29ab5b7aa.png)




ブラウザでワードプレス画面を見ても反映されています。

この辺まではすぐに情報見つかるので詳しくはググッてください。

特に意味のないコードです。
用が済んだら設定画面からブログ名を元に戻しておいてください。





## ワードプレスの設定メニューとオプション

このテーブルはワードプレス本体だけでなく、プラグインで使用することもあるようです。
大体作成方法は想像つくと思います。

ただワードプレスの設定メニューでは、このオプション編集のための便利な方法があるみたいです。
それがregister_setting()とsettings_fields()を使った方法です。
ただ情報が少なく(ただググるの手を抜いただけ)、ソースコードを読みながら覚えただけなので間違ってる可能性もあります。
その時は教えてくださいー！

っと、その前に簡単なプラグインを作ってみます。

んーと、タイトルが最小文字数〜最大文字数以外の時、コンテントのメッセージを変えるという、誰も喜ばない、使いみちのないプラグインを作ってみます。


```php
add_action('the_content', function($content){
    $msg = get_option('dicon_message', 'だいこんらん！　だいこんらん！');
    $min = (int)get_option('dicon_min', 3);
    $max = (int)get_option('dicon_max', 20);

    $title = get_post()->post_title;
    $strlen = mb_strlen($title);

    // タイトルの文字数が最小値未満か、最大値を超えていたら、
    // 特定のメッセージにすり替える
    if($strlen < $min || $strlen > $max)
    {
        return $msg;
    }

    return $content;
});
```

![wp-content-plugin.png](https://qiita-image-store.s3.amazonaws.com/0/205767/e21deeb2-20db-8e25-fb31-085edd33a5f7.png)




タイトルの文字数が3未満、20を超えていたら本文が「だいこんらん！　だいこんらん！」になります。
何度もいいますが、使いみちは無いと思います。



必要なオプションは３つ。「最小文字数」「最大文字数」「メッセージ」です。

|オプション名|オプション値|
|:--|:--|
|dicon_message|メッセージ|
|dicon_min|最小文字数|
|dicon_max|最大文字数|

これらのオプションは後で管理画面から編集可能にしようと思います。
まずはこのコードの意図を理解してください。

ざっくりコードを説明すると、

    $title = get_post()->post_title;

余計なフィルタがかかってると邪魔なのでget_the_title()やthe_title()などは今回は使いません。

    $min = (int)get_option('dicon_min', 3);

get_option()でdicon_minの値(整数値)を取得しようとしてますが、まだdicon_minは設定してません。
その場合は第二引数の3を取得します。


---

今度はフォームから編集出来るようにします。


```php
add_action('the_content', function($content){
    $msg = get_option('dicon_message', 'だいこんらん！　だいこんらん！');
    $min = (int)get_option('dicon_min', 3);
    $max = (int)get_option('dicon_max', 20);

    $title = get_post()->post_title;
    $strlen = mb_strlen($title);

    // タイトルの文字数が最小値未満か、最大値を超えていたら、
    // 特定のメッセージにすり替える
    if($strlen < $min || $strlen > $max)
    {
        return $msg;
    }

    return $content;
});


add_action('admin_init', function(){

    register_setting('dicon-group', 'dicon_message');
    register_setting('dicon-group', 'dicon_min');
    register_setting('dicon-group', 'dicon_max');

});


add_action('admin_menu', function(){

    // 設定にサブメニューぶら下げます。
    add_options_page(
        'おでんグループ',
        'おでんグループ',
        'manage_options',
        'oden-options',
        function(){
            include(dirname(__FILE__) . '/oden-group-form.php');
        }
    );

});
```

新たにadmin_initとadmin_menuのアクションをフックします。
メニューはadd_options_page()で「設定」に対して追加しています。
そしてoden-group-form.phpを設定画面のページとして読み込んでます。

```php
<form method="post" action="options.php">

  <input type="text" name="dicon_message" id="dicon_message" value="<?php form_option('dicon_message'); ?>" />メッセージ<br />
  <input type="text" name="dicon_min" id="dicon_min" value="<?php form_option('dicon_min'); ?>" />最小値<br />
  <input type="text" name="dicon_max" id="dicon_max" value="<?php form_option('dicon_max'); ?>" />最大値<br />

  <?php settings_fields('dicon-group'); ?>

  <?php submit_button(); ?>

</form>
```



![wp2-form-send.png](https://qiita-image-store.s3.amazonaws.com/0/205767/4eaec6db-a615-73fc-8963-4e2f7b45aba3.png)


↑入力して


![wp-form-sent.png](https://qiita-image-store.s3.amazonaws.com/0/205767/b11295a0-565e-25d1-10fe-27ad12a685ad.png)

↑送信後


![wp-option-send-mysql.png](https://qiita-image-store.s3.amazonaws.com/0/205767/35c3c5aa-d5a4-1dbb-b7e2-4a35a29807e4.png)


↑データベースを確認してみると反映されているのがわかります。


**あれ？　update_option()とか使ってないぞ！**

って思ったら上出来！




まずはこの部分。

```php
    register_setting('dicon-group', 'dicon_message');
    register_setting('dicon-group', 'dicon_min');
    register_setting('dicon-group', 'dicon_max');
```

register_setting()は、第一引数でグループ名、そして第二引数をオプション名で登録します。
とりあえず、「登録したぞ！」ということだけ覚えておいてください。

グループ名は適当に決めてください。
ここではdicon-groupが「dicon_message, dicon_min, dicon_max」オプションのグループだよ！
という登録をしています。

このグループ名はフォームからsettings_fields()で使用することになります。



内部的にはグローバル変数(連想配列)の$new_whitelist_optionsに

     $new_whitelist_options[ グループ名 ][] = オプション名;

こんな感じで登録してるようです。
特にDBに保存したり特殊なことしているわけではないと思います(よく見てませんが)
この値は、options.phpが呼び出された時に使用されます。





次にフォームを見ていきます。

    <form method="post" action="options.php">

送信先はoptions.phpにします。
実はワードプレスの設定メニュー(例えば表示設定やディスカッションなど)はoptions.phpに対してPOSTを投げてます。
設定変更の送信後にURLが変わらないのはリダイレクトで元に戻ってるからです。


    <input type="text" name="dicon_message" id="dicon_message" value="<?php form_option('dicon_message'); ?>" />

nameはwp_optionsのオプション名と合わせます。
dicon_messageで送った値は、wp_optionsのオプション名として保存されるようになります。

from_option()はオプション値をエスケープして取得します。



    <?php settings_fields('dicon-group'); ?>


![wp-settings-field.png](https://qiita-image-store.s3.amazonaws.com/0/205767/419c0234-cbab-e127-8ec0-4ec45182191d.png)


```html
  <input type='hidden' name='option_page' value='dicon-group' />
  <input type="hidden" name="action" value="update" />
  <input type="hidden" id="_wpnonce" name="_wpnonce" value="362289c3e8" />
  <input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=oden-options" />
```

↑settings_fields()が出力するものがこちら。

option_pageにグループ名(dicon-group)、
actionにupdate ... これがキモ！

後はリプレイ攻撃対策とリファラーのフィールドですね。



      <?php submit_button(); ?>

送信ボタン、ワードプレスと見た目を合わせます。


---

ではこの流れを軽く追っていきます。
重要な部分にブレークポイントを貼ってるので、デバッグで追っていきます。


エディタはVisual Studio Codeを使ってます。

https://qiita.com/diconran/items/6caed6b15cdda23c9933



![wp2-1-options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/530beca3-7844-b1d0-1798-4e37e33a44d1.png)


↑送信ボタンを押すと、options.phpにPOSTされている。



![wp2-2-options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/5e6b270f-8aa8-9aff-7979-19b159f83504.png)


↑actionにupdateを渡すと


![wp2-3-options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/9ed62aaf-3759-8b36-beb3-34a5ac1e4cec.png)


↑ \$optionsで「dicon_message, dicon_min, dicon_max」が確認できる。
このリストはforeachにより処理され、


![wp2-4-options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/b52649da-95b9-1b8b-d327-6d1cd4386f78.png)



↑update_option( \$option, \$value )でフォームの値がwp_optionsテーブルに反映される。

![wp2-5-options.png](https://qiita-image-store.s3.amazonaws.com/0/205767/d743e3aa-dbbe-8a73-4368-452e0a2f81af.png)


↑ 後はリダイレクトで元の設定画面に戻る(メニューのスラグがoden-options)


まぁこんな感じになってます。


書き忘れてましたが、\$new_whitelist_optionsはoptions.phpにて、
\$whitelist_optionsにマージされてたと思います(うろ覚え)。
そしてこの\$optionsは、

    $options = $whitelist_options[ $option_page ];

となってます。
\$option_pageは「dicon_group」として送られたものです。

register_setting()で設定したものが、この時使われるわけですね。

要約するとこの機能は、\$_POST[] を update_option()に渡す仕組みを提供してるようです。


### 引数

register_setting()の第三引数で引数を指定できます。
get_registered_settings()からその値を取得できます。


```php

add_action('the_content', function($content){
    $msg = get_option('dicon_message', 'だいこんらん！　だいこんらん！');
    $min = (int)get_option('dicon_min', 3);
    $max = (int)get_option('dicon_max', 20);

    $title = get_post()->post_title;
    $strlen = mb_strlen($title);

    // タイトルの文字数が最小値未満か、最大値を超えていたら、
    // 特定のメッセージにすり替える
    if($strlen < $min || $strlen > $max)
    {
        return $msg;
    }

    return $content;
});


add_action('admin_init', function(){

    register_setting(
        'dicon-group',
        'dicon_message',
        array(
            'description' => 'めっせーじ'
        ));

    register_setting(
        'dicon-group',
        'dicon_min',
        array(
            'description' => '最小値'
        ));

    register_setting(
        'dicon-group',
        'dicon_max',
        array(
            'description' => '最大値'
        ));
});


add_action('admin_menu', function(){

    // 設定にサブメニューぶら下げます。
    add_options_page(
        'おでんグループ',
        'おでんグループ',
        'manage_options',
        'oden-options',
        function(){
            include(dirname(__FILE__) . '/oden-group-form.php');
        }
    );

});
```

```php
<form method="post" action="options.php">

<?php
    $settings = get_registered_settings();

    foreach( array('dicon_message', 'dicon_min', 'dicon_max') as $fn )
    {
      $arg = $settings[$fn];
      $desc = $arg['description'];
      echo '<div class="updated">';
      echo $desc;
      echo '</div>';
    }
?>

  <input type="text" name="dicon_message" id="dicon_message" value="<?php form_option('dicon_message'); ?>" />メッセージ<br />
  <input type="text" name="dicon_min" id="dicon_min" value="<?php form_option('dicon_min'); ?>" />最小値<br />
  <input type="text" name="dicon_max" id="dicon_max" value="<?php form_option('dicon_max'); ?>" />最大値<br />

  <?php settings_fields('dicon-group'); ?>

  <?php submit_button(); ?>

</form>
```


![wp2-options-args.png](https://qiita-image-store.s3.amazonaws.com/0/205767/db1985ec-306e-96ee-d80f-2c0ea6f6206f.png)


↑結果。


![wp2-options-args-values.png](https://qiita-image-store.s3.amazonaws.com/0/205767/b562b5cb-3bd1-acbc-38f5-5b04146de6e6.png)


↑でも実際には他の値も取得される。
これ以上調べるの面倒になったので、だれか〜(丸投思考・・・)。




## フォーム作成(Settings API?)

![wp2-settings-writing-1.png](https://qiita-image-store.s3.amazonaws.com/0/205767/7568a4da-88b6-b25e-3818-c1d4a5a2e41e.png)



デフォルトのワードプレスの設定の投稿設定を開いてます。
ここに独自の設定項目をねじ込んでみましょう。


```php
add_action('admin_init', function(){

    add_settings_field(
        'f1',
        'フィールド１',
        function(){
            echo '<input type="text" name="f1" />';
        },
        'writing'
    );

    add_settings_field(
        'f2',
        'フィールド２',
        function(){
            echo '<input type="text" name="f2" />';
        },
        'writing'
    );

});
```

add_settings_field()は内部的にはグローバル変数に追加しているだけだったと思います。

![wp2-settings-writing-2.png](https://qiita-image-store.s3.amazonaws.com/0/205767/7a76f1e8-f038-1a76-163a-897dfae00318.png)


↑ブラウザを更新すると、２つのフィールドが追加されていることが確認出来ます。


![wp2-settings-writing-3.png](https://qiita-image-store.s3.amazonaws.com/0/205767/10d6fe3b-b881-97a7-a8e7-b1a794adea26.png)


この表示をしている箇所が、options-writing.phpのdo_settings_fields('writing', 'default')の部分です。

概念として、フィールドとセクションがあります。
今回はワードプレスに元々あるセクションにフィールドを追加しただけです。
セクションとフィールドは親子のような関係になってます。



### セクションとフィールド

add_settings_section()でセクションを追加し、
add_settings_field()でフィールドを追加、
do_settings_sections()でセクションとフィールドを描画します。
その例です。

```php
add_action('admin_menu', function(){

    add_options_page(
        '大根乱プラグインの設定',
        '大根乱の設定',
        'manage_options',
        'setting_menu',
        function(){
            echo '<form method="post" action="option.php">';
            do_settings_sections('dicon-page');
            submit_button();
            echo '</form>';
        }
    );

});

add_action('admin_init', function(){

    add_settings_section(
        'dicon-section', //セクションID
        'せくしょん１',
        function(){
            echo '<h2>大根混設定！</h2>';
        },
        'dicon-page'
    );

    add_settings_field(
        'f1',
        'フィールド１',
        function(){
            echo '<input type="text" name="f1" />';
        },
        'dicon-page',
        'dicon-section' // セクションID
    );

    add_settings_field(
        'f2',
        'フィールド２',
        function(){
            echo '<input type="text" name="f2" />';
        },
        'dicon-page',
        'dicon-section' // セクションID
    );


});
```

![wp2-sections.png](https://qiita-image-store.s3.amazonaws.com/0/205767/eb7d927f-38bd-378c-e192-6bad7ab20804.png)



add_settings_section()でセクションを追加(IDはdicon-section)し、
add_settings_field()でフィールドをセクション(IDはdicon-section)に２つ追加してます。

セクションIDはセクションとフィールドの親子関係を表せます。
そしてもうひとつ重要な概念がページで、ここではdicon-pageと名づけてます。

|変数名|値|
|:--|:--|
|\$page|dicon-page|
|\$section|dicon-section|

これら関数の引数の\$pageや\$sectionは内部でグローバルな多次元の連想配列の添字になってます。


セクションの追加

    add_settings_section($id, $title, $callback, $page);

↓

    $wp_settings_sections[$page][$id] = array(...);


フィールドの追加

    add_settings_field($id, $title, $callback, $page, $section = 'default', $args = array())

↓

    $wp_settings_fields[$page][$section][$id] = array(...);


このように保存している。
これらの値が実際に使われるのは、

    do_settings_sections('dicon-page');

この時で、do_settings_sections()は、セクションと、その子のフィールドをまとめて出力します。
引数には$pageを渡します。



フィールドだけ表示するdo_settings_fields()もあります。
この関数はdo_settings_sections()の内部でも使われていて、その名の通りフィールドのリストを出力するためのものです。

ただし使うときは注意が必要です。
出力するタグがtrタグの連続なのでtableタグで囲ってやってください。

```php
add_action('admin_menu', function(){

    add_options_page(
        '大根乱プラグインの設定',
        '大根乱の設定',
        'manage_options',
        'setting_menu',
        function(){
            echo '<form method="post" action="option.php">';
            
            echo '<table class="form-table">';
            echo '<tbody>';
            do_settings_fields('dicon-page', 'dicon-section');
            echo '</tbody>';
            echo '</table>';

            submit_button();
            echo '</form>';
        }
    );

});

add_action('admin_init', function(){

    add_settings_section(
        'dicon-section', //セクションID
        'せくしょん１',
        function(){
            echo '<h2>大根混設定！</h2>';
        },
        'dicon-page'
    );

    add_settings_field(
        'f1',
        'フィールド１',
        function(){
            echo '<input type="text" name="f1" />';
        },
        'dicon-page',
        'dicon-section' // セクションID
    );

    add_settings_field(
        'f2',
        'フィールド２',
        function(){
            echo '<input type="text" name="f2" />';
        },
        'dicon-page',
        'dicon-section' // セクションID
    );

});
```

![wp2-fields.png](https://qiita-image-store.s3.amazonaws.com/0/205767/39336408-7554-19dc-8185-03ad6c35448b.png)


当然セクションは表示されてません。
関数の中身を見たほうが早いと思います。


    do_settings_fields('dicon-page', 'dicon-section');

ページとセクションを指定します。


オプションと組み合わせて使うようですけど、同時に理解するのが大変だたので別けて載せました。


## オプションに配列を

書き忘れたので追加しました。

```php
$arr = array(
    'name' => 'diconran',
    'title' => 'おでんといえば大根だよね〜♪',
    'count' => 123
);

if(true){
    add_option('dicon_array', $arr);
}
else{
    $arr = get_option('dicon_array');
    print_r($arr);
}
```

↑これで一度アクセス、if(true)なのでadd_option()が実行される。


```php
$arr = array(
    'name' => 'diconran',
    'title' => 'おでんといえば大根だよね〜♪',
    'count' => 123
);

if(!true){
    add_option('dicon_array', $arr);
}
else{
    $arr = get_option('dicon_array');
    print_r($arr);
}
```

↑今度は条件分岐を変えてget_option()で取得。

面倒なので手を抜いてます。


今度は実際にデータベースにアクセスして調べます。


    dicon@dicon-vm:/var/www/wordpress$ mysql -u root -p

↑MySQLにログイン、パスワードを入力


    mysql> select * from wordpress.wp_options where option_name like 'dicon%';

↑diconから始まる名前を調べる、結果↓

```txt
+-----------+---------------+------------------------------------------------------------------------------------------------------------------+----------+
| option_id | option_name   | option_value                                                                                                     | autoload |
+-----------+---------------+------------------------------------------------------------------------------------------------------------------+----------+
|       547 | dicon_array   | a:3:{s:4:"name";s:8:"diconran";s:5:"title";s:42:"おでんといえば大根だよね〜♪";s:5:"count";i:123;}                | yes      |
|       529 | dicon_max     | 10                                                                                                               | yes      |
|       527 | dicon_message | タイトルの文字数が範囲外です！                                                                                   | yes      |
|       528 | dicon_min     | 4                                                                                                                | yes      |
+-----------+---------------+------------------------------------------------------------------------------------------------------------------+----------+
4 rows in set (0.00 sec)
```

オプションが多い時は連想配列とかに入れるといいみたい。


えーと、次回はAjax周り行きます。