特定のディレクトリの画像を一覧表示する WordPress プラグイン

# 特定のディレクトリにある画像ファイルを取得して一覧表示するプラグインを作ってみた
WordPress のフォーラムで FTP でアップロードされた画像をページに一覧表示したいという質問の投稿があった。
[サーバーにアップされた画像を一覧表示＆自動更新](https://ja.wordpress.org/support/topic/%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e3%81%ab%e3%82%a2%e3%83%83%e3%83%97%e3%81%95%e3%82%8c%e3%81%9f%e7%94%bb%e5%83%8f%e3%82%92%e4%b8%80%e8%a6%a7%e8%a1%a8%e7%a4%ba%ef%bc%86%e8%87%aa%e5%8b%95%e6%9b%b4/)

普通に考えると WordPress のメディアからアップロードされた画像であれば DB にデータが存在しているため、 WordPress の関数を駆使すれば一覧ページを作るのはそれほど難しくはなさそうだが、 FTP で画像はアップロードしているとのことなので直接サーバー内のディレクトリをクロールするしかなさそうであった。
しかも FTP で画像がアップロードされた際に自動更新したいとのことで、定期的にクロールして DB 等に情報を残すか、表示されるたびにクロールするしかなさそうだ。

そこで、まずは一覧表示が可能であるか調査し、最終的にできるだけサーバーに負荷がかからないようにキャッシュを利用したショートコードを作ってみた。
用途からしてそれほど需要はないと思われるが、せっかくなのでプラグイン化して置いておくことにしたので、内容を紹介する。
## プラグインの全貌
いつものように Github に置いておいた。

https://github.com/shokun0803/glob_gallery_plugin

## ショートコードの使い方
WordPress にプラグインをインストールして有効化したら、表示したいページにショートコード `[image_lists]` を記述する。
指定できる属性値とデフォルト値は以下のとおりだ。
* outfile -> 1 ページに表示する画像の数
    * デフォルト ： `4`
* expiration -> 一時的にキャッシュする時間
    * デフォルト ： `60 * 60 * 24`
* directory -> WordPress にインストールされた画像がアップロードされているディレクトリを指定
    * デフォルト ： `wp_upload_dir()['basedir'] . '/*/*/{*.jpg,*.jpeg,*.png,*.webp,*.gif}'`
* img_size → imgタグで指定した画像サイズを属性として指定
元のサイズを使用するには、'origin' を指定
ただし、ディレクトリ指定が相対アドレスの場合、画像サイズは取得せず、属性も指定しません
    * デフォルト ： `width="150"`
## ショートコード指定の例
`[image_lists outfile=2 expiration=60 directory='wp-content/uploads/2020/*/{*.jpg,*.png}' img_size='width="200"']`

## コードの簡単な説明
プラグインを作成するに際していくつかの方法を取ったので簡単に説明する。

```
function image_lists( $atts ) {
	global $post, $wp_rewrite;
	extract( shortcode_atts( array(
		'outfile' => 4,
		'expiration' => 60 * 60 * 24,
		'directory' => wp_upload_dir()['basedir'] . '/*/*/{*.jpg,*.jpeg,*.png,*.webp,*.gif}',
		'img_size' => 'width="150"',
	), $atts ) );
～略～
}

add_shortcode( 'image_lists', 'image_lists' );
```
まずはショートコードを作りこむための WordPress の関数`add_shortcode()`でショートコードを指定する。オリジナルの関数`image_lists()`を作成し属性を読み込めるようにした。
属性はそのまま変数として使用できるように`extract()`を利用して、 WordPress の関数`shortcode_atts()`で属性のデフォルト値を設定している。

```
$origin = $img_size == 'origin'? 'origin' : '';
$pattern = $directory;
$paged = get_query_var( 'paged' );
$transient = 'gallery_' . $post->ID . '_' . $paged;
```
いくつかの変数の初期値を設定しているが`$img_size`は後で`img`タグ生成時にそのまま使用するため属性値に origin が指定された場合の判定値`$origin`を生成している。
また、ページネーションを実現するため WordPress 関数の`get_query_var( 'paged' )`で現在のページ数を取得、ページの ID とページ数を利用して transient キャッシュ用の文字列を作成している。

```
if( false === ( $ret = get_transient( $transient ) ) ) {
	$tag = array();
～略～
	set_transient( $transient, $ret, $expiration );
}
return $ret;
```
サーバー内のディレクトリを何度もクロールするのは負荷が高くなりそうなので、 WordPress の`set_transient()`関数を利用し作られた変数を保存、キャッシュが存在していなければディレクトリをクロールしてタグを生成し返すようにしている。

```
$imgs = preg_grep( '/(\-[\d]+?x[\d]+?)\./', glob( $pattern, GLOB_BRACE ), PREG_GREP_INVERT );
$totalpage = ceil( count( $imgs ) / $outfile );
$imgs = ( $paged == 0 )? array_slice( $imgs, 0, $outfile ): array_slice( $imgs, ( $paged - 1 ) * $outfile, $outfile );
```
サーバー内のディレクトリをクロールするのは PHP の`glob()`関数を利用しており、パターンで複数の条件を検証できるように`GLOB_BRACE`を指定している。
なお、 WordPress はメディアに画像をアップロードするとデフォルトで「ファイル名-縦のサイズx横のサイズ」で複数のサイズの画像を生成する。いろいろ思うところはあるが、 FTP で画像がアップロードされることを前提としているため、 WordPress でアップロードされて自動で生成された追加のサイズ画像は取り除くこととした。
PHP の`preg_grep()`を用いて正規表現で付与された画像サイズを持つファイルを取得した配列から取り除いているが、ちょっと正規表現に自信がない…

また、保存されている画像数が多いとその後のループが重くなってしまうため、現在表示しているページ数から必要な画像を PHP の`array_slice()`で残す仕様とした。

```
		$tag[] = "<figure class=\"is-layout-flex wp-block-gallery-{$post->ID} wp-block-gallery has-nested-images columns-default is-cropped\">";
		foreach( $imgs as $img ) {
			if( substr( $img, 0, 1 ) == '/' ) {
				$img_path = get_site_url() . str_replace( $_SERVER['DOCUMENT_ROOT'], '', $img );
				if( $origin == 'origin' ) {
					$img_size = getimagesize( $img )[3];
				}
			} else {
				$img_path = get_site_url() . '/' . $img;
				if( $origin == 'origin' ) {
					$img_size = '';
				}
			}
			$tag[] = "<figure class=\"wp-block-image size-large\"><img src=\"{$img_path}\" {$img_size} alt=\"\" /></figure>";
		}
		$tag[] = "</figure>";
```
この辺りで画像を表示する`img`タグを生成している。コードは WordPress が生成するギャラリーから借用したので、ある程度デフォルトでも画像がきれいに並ぶと思われる。
また、サーバールートから画像を取得するように設定している場合、実際の画像サイズを PHP の関数`getimagesize()`で取得、配列の 3 番目をそのまま`img`タグに反映している。
ショートコードの属性で`directory`を相対アドレスで指定している場合、画像までの実際の URI が取得できないため、特に指定がない場合は空にすることで対応とした。

```
		$paginate_base = get_pagenum_link(1);
		if( strpos( $paginate_base, '?' ) || !$wp_rewrite->using_permalinks() ) {
			$paginate_format = '';
			$paginate_base = add_query_arg( 'paged','%#%' );
		}else{
			$paginate_format = ( substr( $paginate_base, -1, 1 ) == '/' ? '' : '/' ) . user_trailingslashit( 'page/%#%/', 'paged' );
			$paginate_base .= '%_%';
		}
		$tag[] = paginate_links(array(
			'base' => $paginate_base,
			'format' => $paginate_format,
			'total' => $totalpage,
			'mid_size' => 1,
			'current' => ( $paged ? $paged : 1 ),
			'prev_text' => '<',
			'next_text' => '>',
		));
		$ret = implode( "\n", $tag );
```
最後にページネーションを追加して完了だ。 WordPress の関数`paginate_links()`でページネーションを生成しタグの配列に追加している。
最後に`$tag`の配列をひとつの変数に`implode()`でまとめて`return`する変数を作成している。
この作成した変数を`set_transient()`でキャッシュしているのは前述した通りだ。

以上で特定のディレクトリの画像の一覧を表示するショートコードが使えるようになる。
質問してきた方はサンプルコードを元にしっかりと完成形まで持っていけたようだ。とても素晴らしいことですね。

今回のポイントはキャッシュを持たせたことと全体の画像数が多くてもページごとに必要な数だけループすれば済むようにした辺りだろうか。プラグイン化した時に表示数やキャッシュの時間、ディレクトリを指定できるようにしたので、少しは汎用的に使えるのではないかと思う。