PukiWikiプラグイン開発から学ぶPHP #1

# はじめに
まず、PukiWikiとはウィキのコンテンツ管理ソフト（CMS）です。
ウィキというと例えばWikipediaもウィキですが、複数人で編集することができるサイトのことを示します。
そして今回テーマとなるPukiWikiは和製のPHP言語で書かれたものとなってます。2001年から開発されているかなり昔からあるソフトウェアです
今回はそのソフトウェアのプラグインをつくっていきます。

# 開発
今回は解説のために実用性はあまりない...?、「いいねボタンを追加するプラグイン」を作ります。

## ファイル
- PukiWikiプラグインは **(プラグイン名).inc.php** というファイル名となります 
  ※プラグイン名は小文字英数アンダーバーのみ利用できます。例えば、標準プラグインのファイル名は「edit.inc.php」、「img.inc.php」、「comment.inc.php」となっています

- プラグインは`plugin/` フォルダに入っています

ここではファイル名はlike.inc.phpとします。
つまり、`(PukiWikiルート直下)/plugin/like.inc.php`にプラグインのファイルを作成します。

## 関数
PukiWikiプラグインはあらかじめPukiWiki側で決められた規則的な名前の関数を作成することで動作します。

### plugin_(プラグイン名)_convert()
```
#(プラグイン名)
```
テキストをHTMLに変換している処理で上記のように記述されている箇所でこの関数が呼び出されます。

注意としては返り値がそのまま置き換わるため、XSS攻撃をさせないためにも引数から入力されたものはエスケープすべきです。

複数行も利用できますが、それはPKWKEXP_DISABLE_MULTILINE_PLUGIN_HACKを0にした場合のみですのでPukiWikiの設定がデフォルトのままでは使えないため、これを利用したプラグインを配布する場合は、導入手順にPKWKEXP_DISABLE_MULTILINE_PLUGIN_HACKを0に設定することを書いておくと親切でしょう。

### plugin_(プラグイン名)_inline()
```
Hello &(プラグイン名); !!
```
テキストをHTMLに変換している処理で上記のように記述されている箇所でこの関数が呼び出されます。convertと違うところは行中で呼び出せる点です。

こちらもconvertと同様扱い方によってはXSS攻撃の危険性があるので注意してください。
複数行は使えないです

### plugin_(プラグイン名)_action()
GETやPOSTでプラグインが`https://pkwk.example.com/?plugin=(プラグイン名)`と直接指定された場合に呼び出されます。パラメーター名がpluginではなくcmdの場合もあります。

### plugin_<プラグイン名>_init()
プラグインが初めて呼ばれたときに呼ばれる関数です。あくまで呼ばれたときなのでこのプラグインが使われていないページなどでは呼び出されません。
初期化処理とかに使います。

## like.inc.php
`#like` でいいねボタンが設置できるプラグインをつくるのでconvertを使います

- like.inc.php
```php
<?php
define("PLUGIN_LIKE_FILEPATH", 'likes.json');

// 初期化
function plugin_like_init() {
    $messages['_like_messages'] = [
        'btn_name' => 'いいね',
    ];
    // $_like_messagesというグローバル変数が作られる
    set_plugin_messages($messages)
}

// ?cmd=like or ?plugin=like
function plugin_like_action() {
    global $vars;

    $likes = [];
    // JSONが存在している場合、読み込んで$likesに突っ込む
    if (file_exists($PLUGIN_LIKE_FILEPATH))
        $likes = json_decode(file_get_contents($PLUGIN_LIKE_FILEPATH), true);
    
    if (!isset($likes[$page]))
        $likes[$page] = 0;
    
    $likes[$page]++;

    file_put_contents($PLUGIN_LIKE_FILEPATH, json_encode($likes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

	$retvars['msg']  = $title; // タイトル名
	$retvars['body'] = "";     // 出力するHTML (空の場合はページ)

     // 表示するページをreferのページに
	$vars['page'] = $vars['refer'];
 
	return $retvars;
}

// #like
function plugin_like_convert() {
    global $vars, $_like_messages;

    $page = $vars['page'];

    // XSS対策にhtmlscでページ名をエスケープしておく
	$s_page = htmlsc($page);
     $script = get_page_uri($page);

    $btn_name = $_like_messages['_like_messages']['btn_name'];
    
    $filepath = "likes.json";
    $likes = [];
    if (file_exists($PLUGIN_LIKE_FILEPATH))
        $likes = json_decode(file_get_contents($PLUGIN_LIKE_FILEPATH), true);
    
    $count = isset($likes[$page]) ? $like[$page] : 0; 
    
    return <<<EOD
    <form action="{$script}" method="post">
        <input type="hidden" name="refer"  value="{$s_page}" />
        <input type="hidden" name="plugin" value="like">
        <input type="submit" value="{$btn_name}: {$count}">
    </form>
EOD;
}
```

### $varsについて
このグローバル変数にはPukiWikiで使われているデータや `$_POST` と `$_GET` がマージされたものが入っている配列となってます。
?cmd=aaaならば`$vars['cmd']`には"aaa"が入っていることになります。

### htmlscについて
https://pukiwiki.sourceforge.io/dev/?PukiWiki/Plug-in%E3%81%AE%E4%BB%95%E6%A7%98#d9101c2f

上記リンクの「htmlspecialchars 関数についての注意」に記述されている通り、
> htmlspecialchars 関数を用いる際に3番目のパラメータまで全て明示して使用するか、PukiWiki 1.5以降で定義されている互換関数htmlsc を代替として利用するようにしてください。

とのことです。
つまり、文字エンコーディングが違って文字化けする可能性があるため、PukiWiki本体で準備されているhtmlscを使うのが良いとのことです。