0からわかるWordPressプラグイン開発（スッテプ・バイ・ステップ）


はじめまして。ベンチャーでWebエンジニアをしています。
事業特性上、PHP+WordPressを触ることが多いのですが、本格的なプラグイン開発はたまにしか行わないのでよく手順やコツを忘れてしまいます。
備忘録も兼ねて、チュートリアル形式でWordPressプラグイン開発の手順をまとめておきたいと思います。

完成したソースコードは下記で公開しています。
[CustomIndexBanner](https://github.com/TomoakiTANAKA/CustomIndexBanner/tree/master)

## 前提
### 対象読者
* WordPressの基本構造を理解している（hook/filter）
* WordPressのプラグインをつくりたい人、開発のコツを知りたい人

### 環境

* WordPress 4.9.7

利用しているWordPressのversionは上記の通りですが、versionに依存したテクニックは特にないので、どのversionでも問題ないかと思います。

## 開発するプラグインのゴールイメージ

下記の機能を実装していきます。

* 管理画面のトップレベルに新しい設定を追加する
* 新しい設定画面からデータが保存できる
* 設定を呼び出すことができる

## チュートリアル

チュートリアルの全体像は下記の通りです。

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

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


ここでのゴールは、下記の通りです。
* プラグイン作成して、WordPressに認識してもらえるようにする

wp-cliで雛形を一度に作成する方法もありますが、ここでは手作業で作っていきます。

```
cd {path_to_plugin_directory}
mkdir custom-index-banner
cd custom-index-banner
touch custom-index-banner.php
```


```php:custom-index-banner.php
<?php
/*
  Plugin Name: CustomIndexBanner
  Plugin URI:
  Description: indexページに表示可能なバナーの設定
  Version: 1.0.0
  Author: Tomoaki TANAKA
  Author URI: https://github.com/TomoakiTANAKA/CustomIndexBanner
  License: GPLv2
 */

?>
```

WordPressでは、コメントに必要な記述をするだけでプラグインとして読み込んでくれます。
管理画面にアクセスして確認してみます。


![Kobito.wup8dG.png](https://qiita-image-store.s3.amazonaws.com/0/37873/6ab152ea-2849-322d-c94b-367ce2243bb2.png "Kobito.wup8dG.png")

これで、プラグインが認識されました。（まだ何も機能はないので、有効にしても何も起きません）

### 2. メニューにプラグインを表示する

ここでのゴールは、下記の通りです。
* プラグインを有効にしたら、メニューに追加できるようにする

プラグインをメニューに表示するため、コードを書き換えます。
各種関数の詳細は、公式ドキュメントをのぞきましょう。

* [`add_menu_page`](https://developer.wordpress.org/reference/functions/add_menu_page/)

アイコン画像なども自由に変更できますよ（WordPressで使われるようなアイコンは予め用意されている）

メニューに表示を追加するために、コードを書き換えます。
おおよそはコードを見ればわかると思いますが、初期化時にコンストラクター内部でメニューを追加しています。

なお、画面描画ようにcallback関数を定義していないので画面上ではエラーがでます。
あと、submenuを指定してあげないと、wpの内部でPHPのエラーがでるので注意してください。

```php:custom-index-banner.php
<?php
add_action('init', 'CustomIndexBanner::init');

class CustomIndexBanner
{
    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']);

        }
    }

    function set_plugin_menu()
    {
        add_menu_page(
            'カスタムバナー',           /* ページタイトル*/
            'カスタムバナー',           /* メニュータイトル */
            'manage_options',         /* 権限 */
            'custom-index-banner',    /* ページを開いたときのURL */
            [$this, 'show_about_plugin'],       /* メニューに紐づく画面を描画するcallback関数 */
            'dashicons-format-gallery', /* アイコン see: https://developer.wordpress.org/resource/dashicons/#awards */
            99                          /* 表示位置のオフセット */
        );
    }
    function set_plugin_sub_menu() {

        add_submenu_page(
            'custom-index-banner',  /* 親メニューのslug */
            '設定',
            '設定',
            'manage_options',
            'custom-index-banner-config',
            [$this, 'show_config_form']);
    }

} // end of class

?>
```


![Kobito.bFbbJX.png](https://qiita-image-store.s3.amazonaws.com/0/37873/94b61282-3166-1837-9a39-55b79c28187e.png "Kobito.bFbbJX.png")


### 3. HTMLを表示する

先程定義していなかった、HTML表示用のcallback関数を記述します。

```php:functions.php
<?php
// ...

class CustomIndexBanner
	// ...


    function show_about_plugin() {
      $html = "<h1>カスタムバナー</h1>";
      $html .= "<p>トップページに表示するバナーを指定できます</p>";

      echo $html;
    }

    function show_config_form() {
?>
        <h1>カスタムバナーの設定</h1>
<?php
    }

?>

```

あえて、2種類画面描画の方法を記述しました。phpのコードなので自由にかけますが、複雑なUIを記述するときは、後者のほうが見通しが良くなる気がしています。

▼HTMLが表示された
![Kobito.CeABfg.png](https://qiita-image-store.s3.amazonaws.com/0/37873/15f4b355-fa85-0541-4651-42d78442ba73.png "Kobito.CeABfg.png")


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

データを設定できるように、`show_config_form`を改造します。
ポイントをいくつか列挙しておきます。詳細はそれぞれ調べていただければと

* プラグインのデータは、wp_optionsに格納することが多い。get_options関数を使ってデータを取り出す（①）
* nonce（データを安全に取り扱う仕組み）を利用する。（②）
* DBのprefixや、nonceの識別子を区別しやすくするために、定数として定義しておく（③）

個人的には、③が重要です。
プラグイン名に由来する、同じような記述をたくさんするので、コード上でミスが無いようにそれぞれ意味をもたせた定数にしています。


```php:custon-index-banner.php
<?php
// ...

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 . '_';

	// ...
	
    /** 設定画面の表示 */
    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
    }


}
?>
```

保存処理をまだ記述していないので、保存はできませんが、画面はそれらしくなってきました。

▼WordPressのCSSには共通のクラス（スタイル）が用意されているので、WordPressっぽいデザインにきちんとなる
![Kobito.OYzyvN.png](https://qiita-image-store.s3.amazonaws.com/0/37873/399d770f-e7fc-9aad-d115-5c6f4a152a4d.png "Kobito.OYzyvN.png")

### 5. 保存処理

WordPressのプラグイン本体の設定は、`wp_options `（記事に紐づくデータなら、`wp_postmeta`）への保存が一般的です。
このプラグインは、トップページに表示するバナーの画像やURLを変更するので、`wp_options`にデータを保存します。

WordPressのPluginでデータを保存する場合は、

* 管理画面のロードたびに呼ばれる`admin_init`に、保存処理をhookさせる
* $_POSTに、送信したデータが入っているので、そこからデータを取り出す
* 保存する
 
という流れで、実現します。

いろいろなプラグインの保存処理も`admin_init`に対して、いろいろな処理をhookしています。
そのため、`admin_init`にcallbackする処理には、いろいろな条件をつけて処理をガードしてあげる必要があります。

保存は、`update_option`を使います。

```php:custon-index-banner.php

<?php
add_action('init', 'CustomIndexBanner::init');

class CustomIndexBanner
{
 	// ・・・
 	
 	// config画面のslug
 	const CONFIG_MENU_SLUG  = self::PLUGIN_ID . '-config';
 
    static function init()
    {
        return new self();
    }

    function __construct()
    {
        if (is_admin() && is_user_logged_in()) {
        
        	 // コールバック関数定義
            add_action('admin_init', [$this, 'save_config']);

        }
    }

    /** 設定画面の項目データベースに保存する */
    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)) {
            
                // 保存処理
                $key   = 'title';
                $title = $_POST($value['title']) ? $_POST['title'] : "";
                                      
                update_option(self::PLUGIN_DB_PREFIX . $key, $title);
                $completed_text = "設定の保存が完了しました。管理画面にログインした状態で、トップページにアクセスし変更が正しく反映されたか確認してください。";
                
                // 保存が完了したら、wordpressの機構を使って、一度だけメッセージを表示する
                set_transient(self::COMPLETE_CONFIG, $completed_text, 5);
                
                // 設定画面にリダイレクト
                wp_safe_redirect(menu_page_url(self::CONFIG_MENU_SLUG), false);
            }
        }
    }
    
} // end of class

?>

```

## まとめ
以上、WordPressのプラグインの開発チュートリアルをお届けしました。
全体像がわかってしまえば、ここの実装はWordPressに用意されている機能を呼び出したりすれば良いので、そこまで迷わないはず。