WordPressプラグイン(設定画面追加＋データ保存)のはじめかた

## はじめに

[こちら](https://qiita.com/TanakanoAnchan/items/f1191c4c008f0a2b7c2e)のページのソースが正しくないため修正しました。細かい説明は元記事をご確認ください。

[0からわかるWordPressプラグイン開発（スッテプ・バイ・ステップ）](https://qiita.com/TanakanoAnchan/items/f1191c4c008f0a2b7c2e)

* 実行時エラーの修正
* 更新後のメッセージ表示を追加

  (元記事はWordPress4.9でしたが、 6.0.3で確認)

## 機能
* 管理画面のメニューに項目追加
* 設定画面を追加
* DBに入力した項目を保存する

![img40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/e7db2710-beac-f430-fdc5-ca6f8d4a3b7b.png)


## チュートリアル

  1. プラグインの雛形を作成する
  2. メニューにプラグインを表示する
  3. フォームを作成する
  4. 保存処理

### ソース全体

<details><summary>確認する場合はクリック</summary><div>

```php
<?php
/*
Plugin Name: My Test Plugin
Description: データ保存画面表示プラグイン
Version: 1.0
 */

if (!defined('ABSPATH')) exit;

// ログインユーザのセットアップ後(プラグインの初期化処理に利用)
add_action('init', 'CustomIndexBanner::init');

class CustomIndexBanner
{
    const VERSION                 = '1.0.0';
    const PLUGIN_ID               = 'custom-index-banner';
    const CREDENTIAL_ACTION       = self::PLUGIN_ID . '-nonce-action';
    const CREDENTIAL_NAME         = self::PLUGIN_ID . '-nonce-key';
    const PLUGIN_DB_PREFIX        = self::PLUGIN_ID . '_';
    const MENU_PARENT_SLUG        = self::PLUGIN_ID;
    const MENU_CONFIG_SLUG        = self::PLUGIN_ID . '-config';
    const COMPLETE_TRANSIENT_KEY  = self::PLUGIN_ID . '_complete';

    static function init()
    {
        return new self();
    }

    function __construct()
    {
        if (is_admin() && is_user_logged_in()) {
            // メニュー追加
            add_action('admin_menu', [$this, 'set_plugin_menu']);
            add_action('admin_menu', [$this, 'set_plugin_sub_menu']);

            // コールバック関数定義
            add_action('admin_init', [$this, 'save_config']);
            add_action('admin_notices', [$this, 'admin_notices']);
        }
    }

    /**
     * 親メニューの追加
     */
    function set_plugin_menu()
    {
        add_menu_page(
            'My Test Plugin',           /* ページタイトル*/
            'My Test Plugin',           /* メニュータイトル */
            'manage_options',            /* 権限 */
            self::MENU_PARENT_SLUG,      /* ページを開いたときのURL(menu_slug) */
            [$this, 'show_about_plugin'],       /* メニューに紐づく画面を描画するcallback関数 */
            'dashicons-format-gallery',  /* アイコン see: https://developer.wordpress.org/resource/dashicons/#awards */
            3                           /* 表示位置 (2-ダッシュボード の次) */
        );
    }

    /**
     * サブメニューの追加
     */
    function set_plugin_sub_menu()
    {
        add_submenu_page(
            self::MENU_PARENT_SLUG,  /* 親メニューのslug */
            '設定ページ',             /* page_title */
            '設定ページ',             /* menu_title */
            'manage_options',
            self::MENU_CONFIG_SLUG,
            [$this, 'show_config_form']
        );
    }

    /**
     * メニュー(親)の画面
     */
    function show_about_plugin()
    {
        // テキストをechoで表示
        $html = "<h1>親メニュータイトル</h1>";
        $html .= "<p>親メニュー本文</p>";

        echo $html;
    }

    /**
     * サブメニュー画面
     */
    function show_config_form()
    {
        // wp_optionsのデータを取得して表示
        $title = get_option(self::PLUGIN_DB_PREFIX . "_title");
?>
        <div class="wrap">
            <h1>設定ページタイトル</h1>

            <form action="" method='post' id="my-submenu-form">
                <?php // nonceの設定
                ?>
                <?php wp_nonce_field(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME) ?>

                <p>
                    <label for="title">タイトル：</label>
                    <input type="text" name="title" value="<?= $title ?>" />
                </p>

                <p><input type='submit' value='保存' class='button button-primary button-large'></p>
            </form>
        </div>
<?php
    }

    /**
     * 設定画面の項目データベースに保存する
     */
    function save_config()
    {

        // nonceで設定したcredentialのチェック
        if (isset($_POST[self::CREDENTIAL_NAME]) && $_POST[self::CREDENTIAL_NAME]) {
            if (check_admin_referer(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME)) {
                // 変更前の値
                $prev_title = get_option(self::PLUGIN_DB_PREFIX . "_title");

                // 保存処理
                $title = $_POST['title'] ?? "";
                update_option(self::PLUGIN_DB_PREFIX .  '_title', $title);

                // 処理後のメッセージをtransientにセット
                $completed_text = "設定を保存しました。({$prev_title}⇒{$title})";
                set_transient(self::COMPLETE_TRANSIENT_KEY, $completed_text, 5);
            }
        }
    }

    /**
     * 保存完了メッセージ
     */
	public function admin_notices()
	{
        global $pagenow;
        if ( $pagenow != 'admin.php' ) {
            return;
        }

        // 保存したメッセージがあれば表示する
		if ($notice = get_transient(self::COMPLETE_TRANSIENT_KEY)){
		?>
		<div id="message" class="notice notice-success is-dismissible">
			<p><?=$notice?></p>
		</div>
		<?php
		}
	}

} // end of class

```
</div></details>

### 1. プラグインの雛形を作成する

プラグイン名を`my-test-plugin`とする。
* wp-content/plugins/ の下にプラグイン用のディレクトリを作成する
```bash
cd ./wp-content/plugins/
mkdir my-test-plugin
```

* プラグインファイルを作成する(プラグイン名と同名のファイルを作成する)
```bash
touch my-test-plugin.php
```

* ファイルの先頭にプラグイン情報を記述する

これで、WordPressからプラグインとして認識されます
```php
<?php
/*
Plugin Name: My Test Plugin
Description: データ保存画面表示プラグイン
Version: 1.0
 */
```

![img10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/9d70472d-1d34-ccaa-12eb-8f3fcdb1248c.png)


### 2. カスタムメニューを追加

メニューに「My Test Plugin」というメニューを追加する。ダッシュボードの次に追加するため、$positionに`2`を設定

![img20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/98cca767-4a38-7629-0861-1b0cd8bc15ea.png)



```php
<?php
/*
Plugin Name: My Test Plugin
Description: データ保存画面表示プラグイン
Version: 1.0
 */

if (!defined('ABSPATH')) exit;

// ログインユーザのセットアップ後(プラグインの初期化処理に利用)
add_action('init', 'CustomIndexBanner::init');

class CustomIndexBanner
{
    const VERSION           = '1.0.0';
    const PLUGIN_ID         = 'custom-index-banner';
    const MENU_PARENT_SLUG  = self::PLUGIN_ID;
    const MENU_CONFIG_SLUG  = self::PLUGIN_ID . '-config';

    static function init()
    {
        return new self();
    }

    function __construct()
    {
        if (is_admin() && is_user_logged_in()) {
            // メニュー追加
            add_action('admin_menu', [$this, 'set_plugin_menu']);
            add_action('admin_menu', [$this, 'set_plugin_sub_menu']);

            // コールバック関数定義
            add_action('admin_init', [$this, 'save_config']);
            add_action('admin_notices', [$this, 'admin_notices']);
        }
    }

    /**
     * 親メニューの追加
     */
    function set_plugin_menu()
    {
        add_menu_page(
            'My Test Plugin',           /* ページタイトル*/
            'My Test Plugin',           /* メニュータイトル */
            'manage_options',            /* 権限 */
            self::MENU_PARENT_SLUG,      /* ページを開いたときのURL(menu_slug) */
            [$this, 'show_about_plugin'],       /* メニューに紐づく画面を描画するcallback関数 */
            'dashicons-format-gallery',  /* アイコン see: https://developer.wordpress.org/resource/dashicons/#awards */
            3                           /* 表示位置 (2-ダッシュボード の次) */
        );
    }

    /**
     * サブメニューの追加
     */
    function set_plugin_sub_menu()
    {
        add_submenu_page(
            self::MENU_PARENT_SLUG,  /* 親メニューのslug */
            '設定ページ',             /* page_title */
            '設定ページ',             /* menu_title */
            'manage_options',
            self::MENU_CONFIG_SLUG,
            [$this, 'show_config_form']
        );
    }
```

※メニューは追加されるが、画面はまだ表示されない(画面描画は未作成)

次に画面を表示する`show_about_plugin ()`、`show_config_form()`を実装する

### 3. フォームを作成する

* 親画面

![img30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/18048b2f-0db2-8577-2928-3e2f1290700c.png)


echo でhtmlを出力
```php
function show_about_plugin()
{
    // テキストをechoで表示
    $html = "<h1>親メニュータイトル</h1>";
    $html .= "<p>親メニュー本文</p>";

    echo $html;
```

* 子画面

![img31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/03359140-2c26-085c-5d57-742ebef0869c.png)


タグで出力

```php
function show_config_form()
{
?>
  <div class="wrap">
      <h1>設定ページタイトル</h1>

      <form action="" method='post' id="my-submenu-form">
```

* ソース

```php
    const CREDENTIAL_ACTION = self::PLUGIN_ID . '-nonce-action';
    const CREDENTIAL_NAME   = self::PLUGIN_ID . '-nonce-key';
    const PLUGIN_DB_PREFIX  = self::PLUGIN_ID . '_';

/* ～～　途中略　～～ */

    /**
     * メニュー(親)の画面
     */
    function show_about_plugin()
    {
        // テキストをechoで表示
        $html = "<h1>親メニュータイトル</h1>";
        $html .= "<p>親メニュー本文</p>";

        echo $html;
    }

    /**
     * サブメニュー画面
     */
    function show_config_form()
    {
        // wp_optionsのデータを取得して表示
        $title = get_option(self::PLUGIN_DB_PREFIX . "_title");
?>
        <div class="wrap">
            <h1>設定ページタイトル</h1>

            <form action="" method='post' id="my-submenu-form">
                <?php // nonceの設定
                ?>
                <?php wp_nonce_field(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME) ?>

                <p>
                    <label for="title">タイトル：</label>
                    <input type="text" name="title" value="<?= $title ?>" />
                </p>

                <p><input type='submit' value='保存' class='button button-primary button-large'></p>
            </form>
        </div>
<?php
    }
```

### 4. 保存処理

保存処理と、処理後のメッセージ表示のためフックを「function __construct()」に追加する

|  フック名  |  説明  |
| ---- | ---- |
|  admin_init  |  管理画面ページ表示時に最初に呼び出される |
|  admin_notices  | 更新通知  |


```php
    add_action('admin_init', [$this, 'save_config']);
    add_action('admin_notices', [$this, 'admin_notices']);
```

* `save_config`にデータの保存処理`update_option()`を実装
* `admin_notices`で、保存後のメッセージを表示する

* ソース

```php
    const COMPLETE_TRANSIENT_KEY  = self::PLUGIN_ID . '_complete';

/*  ～～ 途中略 ～～  */

            // コールバック関数定義
            add_action('admin_init', [$this, 'save_config']);
            add_action('admin_notices', [$this, 'admin_notices']);

/*  ～～ 途中略 ～～  */

    /**
     * 設定画面の項目データベースに保存する
     */
    function save_config()
    {

        // nonceで設定したcredentialのチェック
        if (isset($_POST[self::CREDENTIAL_NAME]) && $_POST[self::CREDENTIAL_NAME]) {
            if (check_admin_referer(self::CREDENTIAL_ACTION, self::CREDENTIAL_NAME)) {
                // 変更前の値
                $prev_title = get_option(self::PLUGIN_DB_PREFIX . "_title");

                // 保存処理
                $title = $_POST['title'] ?? "";
                update_option(self::PLUGIN_DB_PREFIX .  '_title', $title);

                // 処理後のメッセージをtransientにセット
                $completed_text = "設定を保存しました。({$prev_title}⇒{$title})";
                set_transient(self::COMPLETE_TRANSIENT_KEY, $completed_text, 5);
            }
        }
    }

    /**
     * 保存完了メッセージ
     */
	public function admin_notices()
	{
        global $pagenow;
        if ( $pagenow != 'admin.php' ) {
            return;
        }

        // 保存したメッセージがあれば表示する
		if ($notice = get_transient(self::COMPLETE_TRANSIENT_KEY)){
		?>
		<div id="message" class="notice notice-success is-dismissible">
			<p><?=$notice?></p>
		</div>
		<?php
		}
	}
```

### 参考ページ
[0からわかるWordPressプラグイン開発（スッテプ・バイ・ステップ）](https://qiita.com/TanakanoAnchan/items/f1191c4c008f0a2b7c2e)

[WordPressでプラグインを使わずに管理(投稿)画面にメッセージを通知](http://www.de2p.co.jp/tech/wordpress/admin-notices/)