かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

WordPress taxonomy.phpのページ現在のページのtermを取得したい。

taxonomy.phpのページ、つまりタクソノミーのターム(term)のアーカイブページで現在のタームtermを取得する方法。

get queried object を使う

現在クエリされているオブジェクトを取得します。例えば:

タクソノミーのアーカイブページならこんな感じ。

<?php
$queried_object = get_queried_object();
// $queried_object は WP_Term Object 
$term_id = $queried_object->term_id;

カスタム投稿タイプのときは保証されてないっぽい (WordPress 4.3以降)

英語のドキュメントに気になることが書いてありました。

Note that precedence plays an important role. As an example, if you visit a custom post type archive while also passing in a taxonomy term (ie. /my-post-type/?my_taxonomy=term), the request is both a post archive and a taxonomy archive. In this case it isn't obvious that get_queried_object() will return the term object and not the post type object (as of WordPress 4.3). The implication is that you cannot necessarily rely on get_queried_object() returning a post type simply because is_post_type_archive() is true.

出典: Function Reference/get queried object « WordPress Codex

WordPress 4.3以降では、カスタム投稿のタクソノミーのターム(term)アーカイブページではget_queried_object()がpost type objectではなく、term objectを返すことは保証していないって感じっぽい?

ということで試してみました。

WordPress v5.0.3
カスタム投稿タイプ(post_type) talent を作成してタクソノミ(taxonomy) talent_typeを作成してターム(term)にcute, cool を作成しました。図にするとこんな感じ

post_type: talent
  |- taxonomy: talent_type
       |- cute
       |- cool

cuteの投稿を作成し、coolの投稿は0件のままにします。
それぞれのカスタム投稿タイプのtalent_typeアーカイブページは下記の通り

アーカイブのテンプレートtaxonomy-talent_type.php

<?php
$queried_object = get_queried_object();
var_dump($queried_object);
$termName = $queried_object->name;
echo $termName;

結果

cute (投稿有り)

$queried_object => (WP_Term) Object
$termName => 'cute'

cool (投稿無し)

$queried_object => (WP_Term) Object
$termName => 'cool'

保証されてないっぽいということでしたが、結果は投稿があってもなくてもget_queried_object()termのオブジェクトが取得できていました。
ただ保証されてないという事なので、$wp_query->query_varsからタームのSlug名を取得する方法のほうが安全なように思います。

query_varsからtermオブジェクトを取得する方法

taxonomy-talent_type.php

<?php
$termSlug = get_query_var('talent_type');
$term = get_term_by('slug', $termSlug, 'talent_type');
$termID = $term->id;

この方法でも試してみた所、アーカイブの投稿があってもなくても term オブジェクトを問題なく取得することが出来ました。
保証されていない方法より、確実な方法にしておく方がWordPressコアアップデートとかで挙動が変わってバグるということが発生しにくいでしょうから良いと思います。ご安全に!
 
WordPressの投稿が増えるときは大抵久しぶりにWordPress案件やってる時。
前にも似たようなこと書いた気がすると思ったらやっぱり書いてた。


[参考]

エアコンついてないのに壁の薄すぎて部屋の中で凍死しそうだったので、これ👇買いました。今の所かなり快適になりました。(尚電気代...

建築技術も上がっているはずなのに、未だに気密性がなく非人道的なアルミサッシの小屋が多いのナンデ?
昔の人は「夏を旨とすべし」とは言ったけれど今の技術なら室内の湿度の問題を解決したまま気密性高くして夏は涼しく冬は温かいお家造れるはずなのに…
Hei-Sayも終わろうとするのに未だに人に合わせるじゃなくて、モノに人が合わせる戦前マインドと人名よりコスト優先なんですかねー…
辛いからお金持ちになって自分でお家作りたい。

WordPress PHP 7.x でデータベース接続確立エラー

PHP 7.3にして、WordPressをみてみたら「データベース接続確立エラー」の表示。お久しぶりっす!!
データベースのユーザー名・パスワードに変更がない・MySQLのバージョンを変更していないならphp.iniでの接続設定に問題がありそう。

mysqlのsocketの設定をみてみる

php.iniの場所を調べる

$ php --ini
Configuration File (php.ini) Path: /Users/<USER_NAME>/.phpbrew/php/php-7.3.1/etc
Loaded Configuration File:         /Users/<USER_NAME>/.phpbrew/php/php-7.3.1/etc/php.ini

php.iniを開いてdefault_socketで検索。
mysqli.default_socketに何も設定されてなかったのが原因のようです。

[MySQLi]
; Default socket name for local MySQL connects.  If empty, uses the built-in
; MySQL defaults.
; http://php.net/mysqli.default-socket
mysqli.default_socket = /tmp/mysql.sock

PHP7.3とPHP5.6のphp.iniにあるdefault_socketの違いのメモ

socket PHP7.3 PHP5.6
pdo_mysql.default_socket あり あり
mysql.default_socket なし あり
mysqli.default_socket あり あり

PHP7ではmysql_connect関数が削除されたためでしょう。mysql.default_socketが存在していません。
PHP5系のときはmysql.default_socketだけ設定しておけばWordPressとのデータベース接続は出来ていたのですが、PHP7からはmysqli.default_socketにsocketのパスを設定しておかなければWordPressが「データベース接続確立エラー」になってしまいます。pdo_mysqlだけではWordPressはダメみたいです。(PHP5.xでもmysqliに設定しておくほうが良さそうな気がしますが...

PHP7を入れ直したので設定し忘れでした... orz


[参考]

mysql_connect
警告 この拡張モジュールは PHP 5.5.0 で非推奨になり、PHP 7.0.0 で削除されました。 MySQLi あるいは PDO_MySQL を使うべきです。
出典: PHP: mysql_connect - Manual

詳細! PHP 7+MySQL 入門ノート

詳細! PHP 7+MySQL 入門ノート

WordPress pre_get_posts内でSQLをカスタムするフィルターを使う時に注意すること

他人が作った古のWordPressテーマをバージョンを上げずにカスタマイズするお仕事で辛みを感じている今日此頃。如何お過ごしでしょうか? (季節の挨拶)

カスタムフィールドやORDER BYなど複雑なクエリで取得したいような時、posts_requestposts_joinposts_orderbyというフィルターを使うとSQLを直接変更してデータを取ってくることができます。

WP_Query内で使用する場合はwp_reset_postdata()で元のクエリに戻せるのでよいのですが、大元のwp_queryを変更するpre_get_posts内でこれらのフィルターを使っていると該当するページ内でWP_Queryget_postsなど使用していると、これらのサブクエリにもpre_get_posts内で設定したフィルターがかかりSQLが変更されてしまい意図しないデータなってしまうの場合があるので注意が必要です。

e.g.

カスタム投稿 アイカツカードaikatsu_cardアーカイブページではカスタムフィールドcard_nocard_typeとで並び替えたいような場合。($wp_query->set()で実現できそうな気もするけど敢えてfilterで)

  1. 投稿に紐づくカスタムフィールドcard_no, card_type を取得する
  2. card_no の値、card_typeの値で並び替え
<?php // functions.php
add_action('pre_get_posts', 'my_custom_wp_query');
function my_custom_wp_query( $wp_query ) {
  // WP_Query に影響を与えないようにメインクエリに限定
  if( $wp_query->is_main_query() ) {
    // カスタム投稿 `aikatsu_card`
    if( is_post_type_archive('aikatsu_card') ) {
      add_filter('posts_join', custom_card_archive_join, 10, 2);
      add_filter('posts_orderby', 'custom_card_archive_orderby', 10, 2);
    }
  }
}
// joinのSQLを変更するフィルター
function custom_card_archive_join($join, $query) {
  global $wpdb;
  $prefix = $wpdb->prefix;
  $join .= ""
    ." INNER JOIN {$prefix}postmeta AS card_no"
    ." ON card_no.post_id = {$prefix}posts.ID"
    ." AND card_no.meta_key = 'card_no'"
    ." INNER JOIN {$prefix}postmeta AS card_type"
    ." ON card_type.post_id = {$prefix}posts.ID"
    ." AND card_type.meta_key = 'card_type'";
  return $join;
}
// orderbyのSQLを変更するフィルター
function custom_card_archive_orderby($orderby, $query) {
  $orderby = ""
    ."card_no.meta_value ASC"
    ."card_type.meta_value ASC";
  return $orderby;
}

これでカスタム投稿aikatsu_cardアーカイブページは、カスタムフィールドcard_nocard_typeの値順に並び替えられたもので表示されます。(雑にINNER JOINSQL作ったのでカスタムフィールドの値がないものは取得されない)

で、aikatsu_cardアーカイブページのテンプレートファイルなどメインクエリ内でnew WP_QUERY()でサブクエリを実行すると、上記のフィルターがSQLにセットされてしまい意図したデータが取得できなくなるっぽい。(is_main_query内にしているのに影響があるのはちょっと不思議。触っているWordPressv4.1なので、新しいバージョンでは発生しない可能性は否めません...)

wp_queryがセットされた後にフィルターを解除すればOK

This action hook runs immediately after the global WP class object is set up. The $wp object is passed to the hooked function as a reference (no return is necessary).
出典: Plugin API/Action Reference/wp « WordPress Codex

wpというglobalのWP class objectが設定された後に実行されるアクションフックがあったので、ここでSQLを変更するフィルターを削除すれば、それ以降のクエリ作成時にフィルターがかかることはなくなります。

<?php // function.php
add_action('pre_get_posts', 'my_custom_wp_query');
function my_custom_wp_query( $wp_query ) {
  if( $wp_query->is_main_query() ) {
    if( is_post_type_archive('aikatsu_card') ) {
      add_filter('posts_join', custom_card_archive_join, 10, 2);
      add_filter('posts_orderby', 'custom_card_archive_orderby', 10, 2);
    }
  }
}
// global WP Object 設定後に実行される
add_action( 'wp', 'my_remove_custom_query_filter');
function my_remove_custom_query_filter() {
  global $wp_query;
  if($wp_query->is_main_query() ) {
    if( is_post_type_archive('aikatsu_card') ) {
      // my_custom_wp_query 内で設定した filter を削除する
      remove_filter( 'posts_join', 'custom_card_archive_join');
      remove_filter( 'posts_orderby', 'custom_card_archive_orderby');
    }
  }
}

これで、aikatsu_cardアーカイブページ内でnew WP_Query()get_posts()を実行してもフィルターの影響なくデータを取得することが出来るようになりました。

WP_Queryはサブクエリになるという認識だったので、is_main_query内で実施したフィルターの影響は受けないと思っていたのでハマってしまいました。なんとなくバグっぽい気がしなくもないので、これが今回触っていたのが古いWordPress v4.1だから発生した現象なのかどうか、つまり最新のバージョンでも発生するかどうかという部分は未検証です。
 

フィルターじゃなくて$wp_query->set()WP_Query()のパラメーターで設定できるのが良いとは思うのですが、そうはいかない場合もありますよね…
そして、WordPressのコアはアップデーする前提で運用して欲しい…と切実に思うのでした。 (古い環境で関数が使えるのかどうかとか探すのが大変なので...)


[参考]

本件とは関係ないけどWordPressさん特定のarchive-{post_type}-taxonomy-{taxonomy}.phpみたいな投稿タイプ+タクソノミーなアーカイブのテンプレート名が欲しい…

イケアではないシャーク🦈