WordPress｜ショートコード｜WP_Queryで投稿一覧を表示

（2020/3/24 追記）
このページに記載しているコードには不具合があります。
訂正したものを**[コチラ](https://qiita.com/tomokei5634/items/e23b517f8efacb958b18)**へ記載しています。ぜひ訂正版をご覧ください。

訂正版
→[WordPressショートコード｜年別・年度別で記事一覧を表示｜WP_Queryの使用例](https://qiita.com/tomokei5634/items/e23b517f8efacb958b18)

※いくつかの項目（解説）は、残しておいても良いと思い、本記事は残しております。

---

WordPressで、年数別で投稿記事の一覧を表示させたい、それをショートコードで実装したい、と思いました。
`WP_Query（）` を使って実現します。

※年代別じゃなくても、お好きな条件（カテゴリやタグや...）でフィルタリングして表示させることができます。

# 例）年代別で投稿記事の一覧を表示させたい

## 1） functions.php へコードの追加
* 使用中の theme の `functions.php` へ下記コードを追加（コピペ）してください。

<font color="red">※下記のコードの改訂版は**[コチラ](https://qiita.com/tomokei5634/items/e23b517f8efacb958b18)**です</font>

```php:functions.php
// 〜 省略（既存のfunction）　〜

/* ショートコードで投稿のリスト表示 */

function getPostList($atts)
{
	$args = [
		'post_type' => 'post',
		'posts_per_page' => '-1',
		'orderby' => 'meta_value',
		'order' => 'DESC'
	];

	if ($atts['tag']) {
		$args += ['tag__in' => explode(',', $atts['tag'])];
	}

	if ($atts['year']) {
		$args += ['year' => $atts['year']];
	}

	$wp_query = new WP_Query($args);

	if ($wp_query->have_posts()) {
		$comparison = null;
		echo '<dl>';
		while ($wp_query->have_posts()) {
			$wp_query->the_post();
			$year = get_the_time('Y');
			if ($comparison != $year) {
				$comparison = $year;
				echo '<dt>';
				echo $year;
				echo '</dt>';
			}
			echo '<dd>';
			echo the_time('m/d');
			echo ' - ';
			echo get_the_title('<a href="' . esc_url(get_permalink()) . '">', '</a>');
			echo '</dd>';
		}
		echo '</dl>';
	}

	wp_reset_postdata();
}
add_shortcode("PostList", "getPostList");
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/54944e60-3b98-ab5b-0822-c416faba0f3a.png)



## 2） ショートコード
* 任意の記事にショートコード `[PostList]` を設置してください。
* ショートコードを設置した部分に、年数別で投稿記事の一覧が表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/8bce6540-2bb0-b268-9020-d82847649f11.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/2ded7149-63fe-94a0-11ae-3ba4e7b3c884.png)

## オプション
オプションの例としてタグと年でフィルタリングする方法を示します。
フィルタリングの内容を変更する場合は、下記の解説をご覧ください。

### タグによるフィルタリング
`[PostList tag="3"]`

* ショートコードの引数に `tag` を加えた場合は、そのタグのある記事だけが表示されます。
* ※tagを**ID**で与えてください。
* `tag="2，3"` のように、複数のIDを与えることができます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/8eb7f7bb-15ab-eabe-bd4b-5b9964e75af2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/afb8ad58-f442-4771-4ba2-d155adf37064.png)


### 年号によるフィルタリング
`[PostList year="2019"]`
* ショートコードの引数に `year` を加えた場合は、その年の記事だけが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/52568aef-6fa0-66bb-4325-3a0556a394b3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/238022d9-6da5-105b-a52b-910a6e283293.png)

---

# 解説

```php:functions.php
/* ショートコードで投稿のリスト表示 */

function getPostList($atts)
{
	/* WP_Query()へ渡すパラメータ 「キー => 値」の配列で用意 */
	$args = [
		'post_type' => 'post', // 投稿タイプ「投稿」
		'posts_per_page' => '-1', // 1ページに含める投稿数。「-1」は全ての投稿
		'orderby' => 'date', // 日付で並び替える
		'order' => 'DESC' // 最新から降順
	];

	/* オプション「tag」 引数が存在する場合に「$args」へパラメータを追加 */
	if ($atts['tag']) {
		$args += ['tag__in' => explode(',', $atts['tag'])]; // explode:引数の文字列を配列へ
	}

	/* オプション「year」 引数が存在する場合に「$args」へパラメータを追加 */
	if ($atts['year']) {
		$args += ['year' => $atts['year']];
	}

	$wp_query = new WP_Query($args); // WP_Query()へパラメータ$argsを渡す。$wp_queryに条件にあった記事一覧が入る。

	if ($wp_query->have_posts()) { // $wp_queryに記事がある場合は...
		$comparison = null; // 年数比較のための変数（あとで使う）
		echo '<dl>';
		while ($wp_query->have_posts()) { // $wp_queryに記事が存在するかぎり繰り返す
			$wp_query->the_post(); // 記事の情報を取得する
			$year = get_the_time('Y'); // 記事の日時を$yearへ代入
			if ($comparison != $year) { // その年の最初の記事の年数（$year）は、$comparison とは異なる
				$comparison = $year; // $comparison へ $year を入れる → その年の次の記事からは、「if」の部分がパスされる
				echo '<dt>';
				echo the_time('Y'); // 年数を表示（その年の最初の記事の年数）
				echo '</dt>';
			}
			echo '<dd>';
			echo the_time('m/d'); // 各記事の投稿日
			echo ' - ';
			echo the_title('<a href="' . esc_url(get_permalink()) . '">', '</a>'); // 各記事のタイトル＋パーマリンク
			echo '</dd>';
		} // END while
		echo '</dl>';
	} // END if

	wp_reset_postdata(); // WP_Query() の終わりに書くもの
}
add_shortcode("PostList", "getPostList"); // ショートコードと関数の紐付け
```

## WP_Query()

WP_Query()へパラメータを渡すことで、任意の投稿一覧（投稿情報）を表示されることができます。
パラメータは非常にたくさんあります。ぜひ参考サイトをご一読ください。

【参考サイト】[これは便利！WordPressのWP_Queryでよく使うコードスニペット](https://sole-color-blog.com/blog/265/)
【参考サイト】[関数リファレンス/WP Query](https://wpdocs.osdn.jp/関数リファレンス/WP_Query)

## explode ( delimiter , string )
タグをフィルタリングするためのオプション部 `if ($atts['tag'])` の中で使っている関数です。
`explode` は文字列(string)を区切り文字(delimiter)で分割し、配列として返します。
タグをフィルタリングするためのパラメータ `tag__in` は配列として渡す必要があるため、`explode` を使っています（引数の文字列 `"2,3"` を配列 `[2,3]` にしています）。

## have_posts() と the_post()
「WordPressで記事一覧を取得する際に使う関数」ぐらいで認識しています。
（なにやら `the_post()` には ループ `while( have_posts() )` を止めるためのカウントアップの機能も担っているようです... ）
【参考サイト】[ループ](https://wpdocs.osdn.jp/ループ)

## get_the_time()
ループ中で、対象記事の投稿日時を取得します。

## if ($comparison != $year)
### 年数を１度だけ表示させたい

```php:functions.php（一部）
// 〜省略〜
$comparison = null;
while ($wp_query->have_posts()) {
	$wp_query->the_post();
	$year = get_the_time('Y');
	if ($comparison != $year) {
		$comparison = $year;
		echo '<dt>';
		echo the_time('Y');
		echo '</dt>';
	}
// 〜省略〜
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/238022d9-6da5-105b-a52b-910a6e283293.png)

もし `if ($comparison != $year)` の部分が無かったら、全ての投稿に年数が表示されます。

目的は「年別に投稿を分けて表示させたい」で、同じ年数が連続して表示されるのを避けたかったので、「その年の最初の投稿の年数だけ表示させ、次の投稿からは年数を表示させない（パスする）」ようにしました。


## the_time('Y年n月j日 G時i分')
`echo the_time()` でループ中の投稿日時を表示できます。引数でフォーマットを指定できます。
（例）では各投稿において日時だけを表示させています（年は非表示）。

## the_title( before, after )
`echo the_title()` でループ中の投稿タイトルを表示できます。 `before` に入れたものと `after` に入れたものでタイトルをサンドイッチすることができます。

これを利用して、`before` に `esc_url(get_permalink())` を入れ、タイトルをクリックしたときに、該当ページへ遷移するようにしています。

## wp_reset_postdata()
`WP_Query()` とセットで使うもの。最後につけるもの。ぐらいで認識しています。

【参考サイト】[関数リファレンス/wp reset postdata](https://wpdocs.osdn.jp/関数リファレンス/wp_reset_postdata)

## add_shortcode( tag , func )
ショートコード名と関数を紐付ける関数です。
（例）では `add_shortcode("PostList", "getPostList");` としていますが、これにより投稿記事側で `[PostList]` で使うことができます。

# つまづき・悩み

（2020/3/24 追記）
<font color="red">※コードを改訂したところ「つまづき・悩み」が解決しました。改訂版コードは**[コチラ](https://qiita.com/tomokei5634/items/e23b517f8efacb958b18)**です</font>


## 変数展開が上手くいかない
phpはダブルクォートで囲った文字列中で変数展開を使うことができます。
そこで、当初は下記のようにコードを記載していました。

```php:functions.php（一部）
// 〜省略〜
if ($wp_query->have_posts()) {
	$comparison = null;
	echo '<dl>';
	while ($wp_query->have_posts()) {
		$wp_query->the_post();
		$year = get_the_time('Y');
		if ($comparison != $year) {
			$comparison = $year;
			echo "<dt> ${the_time('Y')} </dt>";
		}
		echo "<dd> ${the_time('m/d')} - ${the_title('<a href="' . esc_url(get_permalink()) . '">', '</a>')} </dd>";
	}
	echo '</dl>';
}

// 〜省略〜
```

<img width="724" alt="スクリーンショット 2020-03-16 16.05.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/1b08fc5f-7a44-8746-0f10-f6b826d91c5c.png">

`<dt>` と `<dd>` の部分の `echo` をまとめて書いたワケです。
結果が...

<img width="967" alt="スクリーンショット 2020-03-16 16.04.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/cb4ca67c-683b-d42b-9d12-0c1673a76b22.png">

`<dt>`タグや `<dd>`タグの外で変数が展開されてしまいました。
これの解決方法は、まだ分かっていません。
原因や解決方法をご存知の方がいらっしゃったら、コメントいただきたいです...

## 更新に失敗しました。 エラーメッセージ: 返答が正しい JSON レスポンスではありません。
<img width="1153" alt="スクリーンショット 2020-03-16 16.56.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/c1b295fa-38bd-6b16-1c53-d15104b8602b.png">

ショートコードを挿入した場合に `更新に失敗しました。 エラーメッセージ: 返答が正しい JSON レスポンスではありません。` というエラーが表示されるようになりました...（更新自体は出来てて、ちゃんとショートコードも生きてます）

ググって `.ｈｔaccess` をいじったりしてみましたが解決せず...
ちなみに、dockerイメージ「wordpress:latest」を使ってローカルで動かしています。

ショートコードの可能性を考え、下記のような単純なもので試してもダメでした。
（更新は適応されるが、エラー表示が出たまま）

```php:functions.php
function view($atts)
{
	echo $atts['text'];
}
add_shortcode("view", "view");
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/e9c36470-dfa6-81b9-833f-ab5e75dbddcf.png)

どうやら、ショートコードで引数を渡したときにエラーがでる模様です。
同じ様な症状がある方がいらっしゃったら、コメントいただきたいです...