Picoのプラグインを作る

Picoのプラグインは、pluginsというディレクトリの中に、所定のメソッドを実装したPHPクラスを配置するだけ。メソッドはそれぞれページレンダリングの特定のタイミングで呼び出されており、それを処理することで、Picoに機能を追加したり、編集機能を設けたりすることが出来るようです。既存のプラグインは、[PicoCMSのWikiページ](https://github.com/picocms/Pico/wiki/Pico-Plugins)にあります(…が、2015/06/04現在はプラグインの仕様が固まる前に作られたものも多く、「これを使ってなんか作る」というより「これを参考にしたりフォークして新規に自分で作る」ほうが早いかもしれません)。

## プラグインをとりあえず作ってみる

プラグインを作るにあたり気をつけるべきことは３つ

 * pluginsディレクトリにファイルを配置すること(サブディレクトリの下でも良い)
 * phpファイルと、中で定義するクラス名は、同じとすること(大文字小文字は区別しない)
 * プラグインフックメソッドに渡される引数が、値渡しか参照渡しかよく見る(これを間違えると、せっかく加工した処理が反映されない なんてことになる)

pluginsディレクトリには、最初から「pico_plugins.php」というファイルが置いてあるので、それをコピーして好きなように作り替えて使うと良いです。

### プラグインのメソッド呼び出しタイミング

ここからが厄介なところ。このプラグインのメソッド呼び出しタイミングですが、見たところ公式のドキュメントには載っていません。ソースコードは前述の通り一つですのでそんなに追いづらくはないのですが、以下にまとめてみました(記載は呼び出し順)

 1. plugins_loaded
 2. config_loaded
 3. request_url
 4. before_load_content
  1. before_404_load_content(読み込もうとしたファイルが存在したときには、呼び出されない
  2. after_404_content(同上)
 5. after_load_content(以降、コンテンツがあってもなくても呼び出される)
 6. before_read_file_meta(一回のアクセスにつきコンテンツファイルの数(contents/ディレクトリにあるmdファイルの数)ぶん呼び出される)
 7. file_meta
 8. before_parse_content
 9. after_parse_content
 10. content_parsed(非推奨)
 11. get_page_data(before_read_file_metaと同様、コンテンツファイル数ぶん呼び出される)
 12. get_pages
 13. before_twig_register
 14. before_render
 15. after_render

とくに上でも括弧書きしたとおり、`before_read_file_meta`と`get_page_data`は、一回のアクセスごとに、コンテンツファイル数ぶん呼び出されます。全てのコンテンツに追加のメタデータを定義したり、そのメタデータに何らかの加工を施したい場合は、これらを使うといいです。

なお、[公式ページのサンプル](http://picocms.org/docs.html#adding-custom-meta)にあるとおり、`before_read_file_meta`ではメタデータフィールドの追加のみが行えます(メタデータの加工は行えません)。before_read_file_metaでメタデータのフィールド名を追加して、get_page_dataでメタデータを参考に処理を追加していく という感じがベストっぽいです。

### お勧めの使い方？
いちおういくつかプラグインを作ってみた感じのメモ

#### config_loaded
config.phpの設定が読める唯一のフックメソッドです。設定値を使う場合はここでデータを取得し、クラスのフィールドに格納しておきましょう。

また、インターネットのコンテンツを受信し、それをページとして追加するようなプラグインを書く場合は、ここで処理をすませておくと良いです。

```php:plugin_demo.php
class Plugin_Demo{

  private $base_url;
  
  public function config_loaded(&$settings) {
    $this->base_url = $settings['base_url'];
    $this->content_dir = $settings['content_dir'];
  }

  // ・・・
}
```

#### request_url
実際にアクセスするURLが確定したタイミングで呼び出されます。タグページなど実際には存在しないページを存在すると見せかける必要があるときに呼び出されています。

#### before_read_file_meta
メタデータのヘッダを追加できるメソッドです。それ以外のことは出来ません。

```php:plugin_demo.php
class Plugin_Demo{

  // ・・・

  public function before_read_file_meta(&$headers)
  {
  	$headers['image'] = 'Image';
  }

  // ・・・
}
```

#### after_parse_content
パースした直後のファイル(マークダウンから変換した後のHTMLデータ)にアクセスできます。
たとえばヘッダタグの番号を書き換えるなど、HTMLに関する加工を行う場合はここで行います。

```php:plugin_demo.php
class Plugin_Demo{

	public function after_parse_content(&$content)
	{
  	$content = preg_replace_callback('/(<\/?h)(\d)([^>]*>)/', function($m){
        return $m[2] + 3 <= 6 ? $m[1] . ($m[2] + 3) . $m[3] : "";
      }, $content);
	}

}
```

#### get_page_data
メタデータとページデータにアクセスできます。メタデータの加工を行う場合はここでやっておくと良いです。

```php:plugin_demo.php
class Plugin_Demo{

  // ・・・
	
	public function get_page_data(&$data, $page_meta)
	{
    $file_url = substr($data["url"], strlen($this->base_url));
    if($file_url[strlen($file_url) - 1] == "/") $file_url .= 'index';
    if (strlen($page_meta['image']) > 0 && preg_match('/^(.+\/)[\w\.-]+?$/', $file_url, $m)) {
      if($page_meta['image'][0] == '.'){
        $data['image'] = $this->base_url . "/" . $this->content_dir . "$m[0]$page_meta[image]";
      }else{
        $data['image'] = $this->base_url . "/" . $this->content_dir . "$m[1]$page_meta[image]";
      }
    } else {
      $data['image'] = NULL;
    }

  // ・・・
}
```

#### get_pages
全てのページ情報にアクセスできるメソッドです。処理が重くなりがちなので、なるべく使わない方が良さそうです。

なお、古いプラグイン仕様ではこのメソッドで「ファイルが存在すれば処理を、しなければそのエントリをpagesリストから消す」という処理をやっているものがあります。

```php:plugin_demo.php
class Plugin_Demo{

  // ・・・

	public function get_pages(&$pages, &$current_page, &$prev_page, &$next_page) {
		$new_pages = array();

		foreach ($pages as $page) {
			// ・・・
		}

		$pages = $new_pages;
	}


  // ・・・
}
```

#### before_render
ほぼ最後に呼び出される処理です。Twig変数に直接アクセスできるので、ページの並び替えや、グループ化して別の変数を作るなどの処理を行う場合はここが良いです。

```php:plugin_demo.php
class Plugin_Demo{

  // ・・・
	
	public function before_render(&$twig_vars, &$twig, &$template) {
		$pages = $twig_vars["pages"];
		$curdir = array();
		// ・・・
		$twig_vars["dir_pages"] = $curdir;
	}

  // ・・・
}
```

### プラグインの呼び出し順は？
変更出来ません。pico.phpの`get_files`メソッドを見た感じ、PHPのディレクトリ処理関数におまかせのようです。

よって、○○のプラグイン処理の後に△△のプラグインの処理を実行して・・・ というようなことは出来ないっぽいです。