最初にコード全体を見せて、次にそれぞれの説明をしていきます。
参考情報
- 月別アーカイブを特定のカテゴリーのみにしたい(teratail.com)
- WordPressの投稿データを、カテゴリー別に引っ張ってくるSQL(Qiita)
コード
青文字が考えた部分です。それ以外は変更していません。
<?php // カテゴリーなし(slug名)のみの月別アーカイブ表示
$year_prev = null;
$slug = 'カテゴリーなし';
// 日本語のエンコード処理
$slug = urlencode($slug);
// エンコードした場合、%が入る。SQLでは %は %% とエスケープが必要if(preg_match("/%/", $slug)):
$slug = str_replace('%','%%', $slug);
endif;
// SQLクエリ
$query = $wpdb->prepare("SELECT DISTINCT MONTH( post_date ) AS month ,
YEAR( post_date ) AS year,
COUNT( id ) as post_count FROM $wpdb->posts
WHERE post_status = 'publish' and post_date <= now( )
and post_type = 'post'
and id IN(
SELECT object_id FROM wp_term_relationships
WHERE term_taxonomy_id = (
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
)
)
GROUP BY month , year
ORDER BY post_date DESC",$slug);
$months = $wpdb->get_results($query);
foreach($months as $month) :
$year_current = $month->year;
if ($year_current != $year_prev){
if ($year_prev != null){?>
</ul>
<?php } ?>
<h3><?php echo $month->year; ?></h3>
<ul class="archive-list">
<?php } ?>
<li>
<a href="<?php bloginfo('url') ?>/<?php echo $month->year; ?>/<?php echo date("m", mktime(0, 0, 0, $month->month, 1, $month->year)) ?>"> <span class="archive-month"><?php echo date("n月", mktime(0, 0, 0, $month->month, 1, $month->year)) ?></span> </a>
</li>
<?php $year_prev = $year_current;
endforeach; ?>
</ul>
以下、青色部分について説明していきたいと思います。
1. エスケープとセキュリティ対策
スラッグのエンコードとエスケープ処理
$slug = urlencode('カテゴリーなし');
// 日本語のエンコード処理
if(preg_match("/%/", $slug)):
$slug = str_replace('%','%%', $slug);
endif;
アルファベットや記号などなら問題ないのですが、日本語などの場合に WordPress では urlencode された値が保存されます。それは % が分だんに入ることになり、SQLクエリでは % は %% とエスケープが必要のため、その措置を入れています。
SQLクエリに変数が含まれる場合には、SQLクエリのエスケープが必要です。esc_sqlもありますが、構文チェックもしてくれる $wpdb->prepare を使うのが推奨されてますね。最初にSQLクエリを入れて、そこに変数の値を含める場合には、%s が文字列、 %dが数字 を入れます。そして、あとから変数を書くというスタンスです。
id IN(
SELECT object_id FROM wp_term_relationships
WHERE term_taxonomy_id = (
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
)
)
です。 %s は スラッグ名をいれることになります。
手順としては次の通りです。
複雑そうに見えますが、順を追ってみてみましょう。
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
ASは別名ということになるのですが、それを使わなければ
SELECT term_taxonomy_id FROM wp_term_taxonomy
INNER JOIN wp_terms ON wp_term_taxonomy.term_id = wp_terms.term_id
// 日本語のエンコード処理
$slug = urlencode($slug);
// エンコードした場合、%が入る。SQLでは %は %% とエスケープが必要if(preg_match("/%/", $slug)):
$slug = str_replace('%','%%', $slug);
endif;
アルファベットや記号などなら問題ないのですが、日本語などの場合に WordPress では urlencode された値が保存されます。それは % が分だんに入ることになり、SQLクエリでは % は %% とエスケープが必要のため、その措置を入れています。
SQLインジェクション対策
$wpdb->prepare("◯◯ %s", $slug);
2. 指定したカテゴリーを条件とするクエリ
id IN(
SELECT object_id FROM wp_term_relationships
WHERE term_taxonomy_id = (
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
)
)
です。 %s は スラッグ名をいれることになります。
手順としては次の通りです。
- wp_terms の slug 情報を元に、wp_term_taxonomy テーブルより指定したカテゴリーに紐づくタクソノミーIDをゲットする
- 指定したカテゴリーに紐づくタクソノミIDを元に、wp_term_relationships テーブルより、投稿データの id 情報(object_id)をゲットする
- 投稿データの id 情報(object_id)を元に、wp_posts より該当する id を条件とする式を完成させる
複雑そうに見えますが、順を追ってみてみましょう。
1. wp_terms の slug 情報を元に、wp_term_taxonomy テーブルより指定したカテゴリーに紐づくタクソノミーIDをゲットする
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
ASは別名ということになるのですが、それを使わなければ
SELECT term_taxonomy_id FROM wp_term_taxonomy
INNER JOIN wp_terms ON wp_term_taxonomy.term_id = wp_terms.term_id
WHERE wp_terms.slug = '%s'
ですね。wp_term_taxonomy テーブルの term_id と wp_terms テーブルの term_id をベースに、wp_term_taxonomy テーブルとtermsテーブルを結合させます。その上で、wp_terms テーブルの slug 内から 指定したスラッグ名を抽出し、ヒットしたデータについて結合したテーブルから term_taxonomy_id を抽出します。
ここで何故結合が必要かというと、スラッグ情報が入っている wp_terms には term_taxonomy_id は存在しないため、2つのテーブルを結合させた上で、必要な情報をゲットするためです。
2. 指定したカテゴリーに紐づくタクソノミIDを元に、wp_term_relationships テーブルより、投稿データの id 情報(object_id)をゲットする
SELECT object_id FROM wp_term_relationships
WHERE term_taxonomy_id = (
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
)
タクソノミー情報(term_taxonomy_id)を元に、wp_term_relationships テーブルから、object_idをゲットする。青文字のところだけですが、これはまぁ分かりやすいかなと思います。
3. 投稿データの id 情報(object_id)を元に、wp_posts より該当する id を条件とする式を完成させる
id IN(
SELECT object_id FROM wp_term_relationships
WHERE term_taxonomy_id = (
SELECT term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS tm ON tt.term_id = tm.term_id
WHERE tm.slug = '%s'
)
)
ゲットした object_id は複数存在する可能性があります。
そのため id = () ではダメで、id IN () を利用する必要があります。それらが含まれていた場合ってことですね。これを WHERE の条件にいれて wp_posts テーブルからデータをゲットするという流れですね。
このSQLクエリが最善かは分かりませんが、カテゴリー情報と投稿(固定ページ含む)データがどのように結びついているか勉強になりますね。実際に phpmyadminツールを利用してデータベースのテーブルデータを見ながら、また参考情報を参考にしながら作ってみました。
2016年12月28日 @kimipooh
0 件のコメント:
コメントを投稿