検索対象にカスタムタクソノミーの文字列も含めたい wp_pagenavi対応

前記事の「検索対象にカスタムタクソノミーの文字列も含めたい」で作成した検索が、ページネーション用に使用しているプラグイン「wp_pagenavi」に対応していなかった。

原因を探ってみる。

query_posts()にページ送り用の情報が送られていないのが原因のようだ。

wp_pagenaviを利用している普通の検索だと、$query_stringにある
s=’検索キーワード’
paged=ページ番号
という情報が自動的に適用される。

検索をカスタマイズしてしまったので、「paged」の情報を追加してquery_posts()を行う必要がある。

修正したのは以下の部分。


//検索結果が0でなければ、query_postsでヒットした投稿のIDを指定して抽出する。

if (!empty($objResult)){
	
	$id_array = '';
	foreach ($objResult as $key => $value) {
	//	echo $value->post_title . ':' . $value->name .  '<br />';
		$id_array[$i] = $value->ID;
		$i++;
	}

	$arg = array(
		'post__in'=>$id_array,
		'post_status'=>'publish',
		'post_type'=>'対象のカスタム投稿タイプ' //対象のカスタム投稿タイプ
	);

	//$query_stringを分解して、wp_pagenaviの情報を配列に追加
	if(!empty($query_string)){
		$query_string_array = explode('&',$query_string);
		foreach ($query_string_array as $q_key => $q_value) {
			$temp = explode('=',$q_value);
			$key_str = $temp[0];
			if ($temp[0] == 'paged'){
				$numb=(int)$temp[1];
				if (is_int($numb)) $arg[$key_str] = $numb ;
			}
		}
	}

	
	query_posts( $arg );

}

$query_stringを分解して、pagedが含まれていれば配列に格納する。
その際に変なもんが入らないよう、整数型にキャストしてチェックし、数字以外が追加されないようにする。

これでページ送りも確認がとれた。

ただ、キーワードを複数にした場合、期待通りの動作をしないことがある。
これは自身の稚拙なSQLに原因があるのだと思う。
あれこれ試してみたが、どうもうまく行かないので、こんど詳しい方にアドバイスを仰ごう。

追記

query_posts()のパラメータ「paged」は、wp_pagenaviの利用に限らず存在してた。

query_posts()のパラメータ指定に、post_statusがpublishであることを付け加えた。
これが無いと非公開の投稿も表示されてしまう。

検索対象にカスタムタクソノミーの文字列も含めたい

同じ問題にハマって検索したら自分の記事が出てきて内容が古くて唖然としたのでアップデート。

Search Everythingを使う。

このプラグインで、カスタムタクソノミ―を検索対象にできる。

インストール後、Search Everythingの設定で、Search custom taxonomiesにチェックをする。
カスタムフィールドを検索にチェックを入れれば、カスタムフィールドも、対象にできる。
ちなみに検索キーワードをハイライトにチェックを入れていたら、昔不具合があったのでそれ以来ここはチェックを外している。

さらに、今回は通常の投稿と、カスタム投稿タイプとで検索結果を別々に表示したかったため、以下の作業も行った。

ワードプレスの検索ボックスのフォームのソースに、以下を追加

<input type="hidden" value="作成したカスタム投稿タイプ" name="post_type" id="post_type">

これで指定したカスタム投稿タイプのみが検索対象になる。

検索結果画面のレイアウトなども、カスタム投稿タイプによって変更したい場合は、
テーマの中にある、search.phpを複製し、search-作成したカスタム投稿タイプ.php
にして行けるかと思ったら、駄目だった。
singleとかarchiveは行けるのに。

検索したら、functions.phpに追記する必要があるらしい。
こちらのサイトでやり方が紹介されています。

カスタム投稿タイプ毎に検索結果を分岐させる方法

大変助かる。

以下は古いので参考にはならない。

カスタム投稿タイプを使っているのだが、検索してみてあることに気づいた。
カスタム投稿タイプのカテゴリやタグ(正確にはタクソノミー)は、Wordpress標準の検索機能だと対象外になっている。

例えばこんな投稿をした場合。

記事タイトル:サラブレッド
カテゴリ:動物
タグ:馬

この場合、「サラブレッド」で検索すると、検索にヒットするが、「動物」や「馬」だとヒットしない。
それはそれでありなのかもしれないけど、自分的にはヒットして欲しいのでカスタマイズしてみる。

カスタマイズするファイル

WordPressで検索した場合、テーマ内にsearch.phpがあると優先的に表示される。
ここに渡される検索の文字列は、get_search_query()で取得できる。
取得した文字列から、検索対象をカテゴリやタグにまで広げてヒットするようにしようと思う。
始める前に、search.phpは別名保存してバックアップしておく。

検索する対象

・カスタム投稿タイプで投稿した記事のタイトル
・カテゴリ
・タグ
の文字列が対象

今回のカスタム投稿タイプでは、記事を表示しないちょっと特殊なケースなので、この3つだけ。
カテゴリとタグは、正確にはregister_taxonomyで作成したカスタムタクソノミー。
カテゴリっぽく見せいてるのは、hierarchicalをtrueにしたもの。
タグっぽく見せているのは、hierarchicalをfalseにしたもの。

これらの3つをどうやって同時に検索するかあれこれ悩んだのだけど、結局直接クエリでやるしかないかなーと思った。

・「カスタム投稿タイプで投稿した記事のタイトル」は、wp_postsのpost_titleフィールド。
・「カテゴリ」と「タグ」は、wp_termsのnameとslugフィールド。

記事投稿ページからタグを追加すると、nameとslugには同じ文字列が入るけど、後からslugだけ英語にするとかは容易に想定される。

ここで問題なのは、wp_postsテーブルとwp_termsテーブルを直接結びつけることの出来るフィールドが無いこと。
なので、間にこの二つのテーブルが必要になる。
wp_term_relationships
wp_term_taxonomy

つまり、4つのテーブルを結びつけた検索クエリが必要となるわけだ・・・。
SQLは勉強不足なので心配だなあ・・・。
※ちなみにWordpressのインストール時に接頭辞を変えていた場合、テーブル名はwp_ではないです。

実際に書いてみる

<?php

//検索キーワードを取得
$keys = get_search_query();
//スペースが全角の場合、半角に変換
$keys = str_replace(' ', ' ', $keys);
//SQLインジェクション用
$keys = mysql_real_escape_string($keys);
//検索キーワードを配列に変換
$keys_array = explode(' ', $keys);

//キーワードの数だけWHERE節を作成する
$sql_where = '';
foreach ($keys_array as $key => $value) {
	if(empty($sql_where)){
		$pre_str = 'WHERE ';
	}else{
		$pre_str = 'AND ';
	}
	$sql_where .= $pre_str . "(0<LOCATE('{$value}',posts.post_title) OR 0<LOCATE('{$value}',t_terms.name) OR 0<LOCATE('{$value}',t_terms.slug)) ";
}

//クエリを実行	
$objResult = $wpdb->get_results(
	$wpdb->prepare(
		"SELECT * FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->term_relationships} t_rel  ON posts.ID = t_rel.object_id
		LEFT JOIN {$wpdb->term_taxonomy} t_term_tx ON t_rel.term_taxonomy_id = t_term_tx.term_taxonomy_id  
		LEFT JOIN {$wpdb->terms} t_terms ON t_term_tx.term_id = t_terms.term_id 
		"
		. $sql_where
));


//検索結果が0でなければ、ヒットした投稿のIDを指定してquery_postsで抽出する。

if (!empty($objResult)){
	
	$id_array = '';
	$i=0;
	foreach ($objResult as $key => $value) {
		$id_array[$i] = $value->ID;
		$i++;
	}
	
	$arg = array(
		'post__in'=>$id_array,
		'post_type'=>'対象のカスタム投稿タイプ' //対象のカスタム投稿タイプ
	);
	
	$posts = query_posts( $arg );

}

//検索結果が0なら、query_postsを行わない。その場合、標準の検索機能が自動的に動作する。
//そのため、何もヒットしなければ、標準の「ヒットしませんでした」画面も自動的に表示される。

?>

重要

このコードは、search.phpの文中にある
if ( have_posts() )
より上に記述する。

私の環境では、これで動作確認が取れた。

詰まったところ

検索の際、完全一致ではなく部分一致でも通るようにしたが、ここでハマった。
最初、WHERE節でLIKEを使用したのだが、何故かワイルドカードの「%」を完全に無視する・・・泣。
クエリを実行する際に、wordpressの機能である$wpdbを利用しているせいかどうか知らないが、結局この問題は解決できなかった。

SQLが未熟な私は色々検索して、結局LOCATEを使うことで問題を回避できた。
解決じゃないのがちょっと残念。
ちなみにLOCATEは初めて使った(・・・というか初めて知った 汗)。

もっと簡単な方法とかありそうだなー。
知らないというのは、損だなー。

テンプレートファイル以外でもWordPressの関数を使いたい。

表示する画像に細工を施すの目的。
なので、テンプレファイル以外で処理する必要があった。

WordPressの関数を使わなくても出来ないことはないのだけど、その場合ソースを覗かれると元の画像のパスとかが表示されてしまう。
それでは本来の目的に添えない。

WordPressの構造などを紹介しているサイトなどを調べてみると、wp-load.phpを読み込むと良いらしい。
さっそく実践してみたところ、とりあえず無事に動作確認が取れた。

ただし、きちんと構造を把握してから使ってないので、若干不安が残る。
Wordpressの構造についても、もっと勉強しないと。

WordPressでcookieを使おうと思ったら

functions.phpに自作のcookie制御用の関数を追加したのだけど、どうも動かないみたいだ。
なので、PHPではなくJavasctiptでcookieを使うことにした。

が、考えてみたら、いつもPHPで書いていたので、Javasctiptで書いたことが無かった。

jQueryで便利なの無いかなーと検索してみたら、あった。

jquery.cookie.js

cookieをセットする場合は、
$.cookie(‘変数名’, ‘値’);

値を取り出す場合は、
$.cookie(‘変数名’);

便利だ。

jquery.cookie.jsの入手はこちらから

カスタム投稿タイプでカテゴリとタグを使う

内容が古いのでアップデート

Custom Post Type Generator
を使う。
カスタム投稿タイプ使用する際に。今までfunctions.php直接編集してたけど、こっちの方が楽。

おしまい。

以下は古い内容なので、参考にならない。

WordPressの投稿にあるようなカテゴリやタグを、カスタム投稿タイプでも使いたい。

function.phpに追加して使用していたのだけど、カテゴリだけではなくタグも利用したくなった。
register_post_typeのsupportsに追加すれば良いのかと思ったけど、違ったのでメモ。

カテゴリもタグも、ようはカスタムタクソノミーを使うのだけど。

カテゴリのような入力欄を記事投稿画面に欲しい場合は、register_taxonomyのhierarchicalをtrueに。
タグのような入力欄を記事投稿画面に欲しい場合は、register_taxonomyのhierarchicalをfalseに。

この二つは併用できる。

具体的にはこんな感じ。
追加する投稿タイプを仮に「products」とした場合。

add_action( 'init', 'create_post_type' );
function create_post_type() {
  register_post_type( 'products', /* post-type */
    array(
      'labels' => array(
        'name' => __( '製品' ),
        'singular_name' => __( '製品' )
      ),
      'public' => true,
      'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields' ,'comments' ),
      'menu_position' =>5,
      'has_archive' => true
    )
  );
  
//カスタムタクソノミー、カテゴリタイプ
  register_taxonomy(
    'products-cat', 
    'products', 
    array(
      'hierarchical' => true, 
      'update_count_callback' => '_update_post_term_count',
      'label' => '製品のカテゴリー',
      'singular_label' => '製品のカテゴリー',
      'public' => true,
      'show_ui' => true
    )
  );
  
//カスタムタクソノミー、タグタイプ
  register_taxonomy(
    'products-tag', 
    'products', 
    array(
      'hierarchical' => false, 
      'update_count_callback' => '_update_post_term_count',
      'label' => '製品のタグ',
      'singular_label' => '製品のタグ',
      'public' => true,
      'show_ui' => true
    )
  );

}

こんな感じで記事の入力欄にカテゴリとタグの入力欄が表示されるようになる。

カスタム投稿タイプのリスト

カスタム投稿タイプを使用すると、メニューなどで一覧を取得したり、現在表示中のページとメニューの関連付けなどをする必要が出てくる。

だけど、これが色々面倒くさい。
ちょっとてまどったのでメモ。

自分の作り方では、固定ページにカスタム投稿タイプで作成したページのメニューとして一覧を表示するケースが多い。
その場合、当然ながら固定ページとカスタム投稿タイプには何の関連性もないのでメニューを作ったりするのが少々めんどい。

function.php内
$post_idはpost_idを渡す

function pageLists($post_id){
$post_type = get_post_type($post_id);
$loop = new WP_Query( array( 'post_type' => $post_type ) );
echo '<ul>';
while ( $loop->have_posts() ) : $loop->the_post();
//送られてきたpost_idからcurrent_postを設定するのが目的
//整数型にキャスト
$temp_id = (int)($loop->posts[$i]->ID);
if ($temp_id == $post_id) $current_str = ' current_page_item' ; else $current_str = '';
echo '<li class="page_item' . $current_str . '"><a href="';
the_permalink();
echo '">';
the_title();
echo '</a></li>';
$i++;
endwhile;
echo '</ul>';
wp_reset_query();
}

$loopを見ると、$loop->posts[$i]->IDに投稿IDが格納されているので、それを整数型にキャストする。
パラメータとして渡された現在表示中の投稿IDと一致すれば、liタグのclassにcurrent_page_itemを追加する。
という流れ。

current_page_itemというクラスは、wp_list_pagesを使ったときに現在表示中のページのリンクに自動的に付与されるclassなので合わせておくと楽。

実際書いたfunctionにはもっと色々書いているが、メモ用に抜粋した。
このままで動くかな?

もっと楽な方法ないかなー。

カスタム投稿タイプの記事をカテゴリ別に表示

ちとEC CUBEお休みでWordpress。

カスタム投稿タイプを作成し、その中でカテゴリ分けを行った。
表示するときに、カテゴリ別に表示したかったのだけど、すこし手間取った。

<?php
$args = array(
	'tax_query' => array(
		array(
			'taxonomy' => 'タクソノミー名',
			'field' => 'slug',
			'terms' => array( 'タームス名' )
		)
	),
	'post_type' => '作成したカスタム投稿タイプ',
	'posts_per_page' => -1
);
$loop = new WP_Query( $args );
while ( $loop->have_posts() ) : $loop->the_post();
?>
必要な処理
<?php endwhile; ?>

タクソノミー名には、register_taxonomyで作成したタクソノミー名を、
タームス名には、カスタム投稿タイプで作成したカテゴリをそれぞれ指定。

wp-pagenaviで404エラー

WordPressでページネーションを行いたい場合、wp-pagenaviは非常に重宝するのだけど、ちょっと問題もある。

パーマリンクを「/%category%/%postname%/」のように設定していた場合、2ページ目以降を404エラーに飛ばしてくれちゃう。

私は、ほぼ全てのケースで「/%category%/%postname%/」を使うので、ここは必ずハマってしまう。
(※このブログでは使ってないが・・・・)

色々調べたら、この問題を解決してくれる便利なプラグインがある。

「FV Top Level Categories」

プラグイン追加のページで検索してインストール、動作確認・・・。

解決しました。

こちらを参考にさせていただきました。

WordPressページ分割プラグインWP-Pagenaviの2ページ目以降が表示されない原因

フォトギャラリーを作成する。

元同僚(通称:アニキ、女性)が、Wordpressを利用したフォトギャラリーを作りたいと言っていたことを思い出した。
少々時間ができたのでチャレンジしてみた。
プラグインを使わず、カスタマイズで実現させる。
元にしたテーマはデフォルトのtwentyeleven。
以下は覚書。

アニキは、

  1. 写真を投稿する
  2. 投稿した写真をサムネイルで一覧表示する
  3. サムネイルをクリックすると、ダウンロードページに行く

と言っていたような。
順番に見ていこう。

1.写真を投稿する。
Wordpressベースなので、記事に写真を載せてアップするのが一番楽かなと考えた。
写真の説明などを書きたい場合などにもそのまま対応できる。

ここでは、写真投稿専用のカスタム投稿タイプを利用することにした。
まず、functions.phpに以下を追加して、カスタム投稿タイプを設定。

/* add custom post type */
add_action( 'init', 'add_post_type' );
function add_post_type() {
  register_post_type( 'photos', /* カスタム投稿タイプ */
    array(
      'labels' => array(
        'name' => __( '写真' ),
        'singular_name' => __( '写真' )
      ),
      'public' => true,
      'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields' ,'comments' ),
      'menu_position' =>5,
      'has_archive' => true
    )
  );
}

これで管理画面の左メニューに、「写真」という項目が追加される。
カスタム投稿タイプを追加

なお、カスタム投稿タイプを設定した場合、何も変更を加えてなくても、パーマリンクを保存しなおさないと表示されない場合があるので注意。
パーマリンクの設定は、管理画面左メニューの「設定」から。

次に、カスタム投稿タイプ表示用の画面を準備。

投稿されたデータの表示は、通常「single.php」が用いられる。
カスタム投稿タイプ専用に表示をアレンジしたい場合、このファイルをベースにして
single-カスタム投稿タイプ名.php
を作成すると、single.phpより優先的に用いられる。
今回は、「photos」というカスタム投稿タイプを作成したので、ファイル名は「single-photos.php」となる。

ファイルを作成したら、中身をカスタマイズ。
single.php

赤枠を書き換え。

<h1><?php the_title(); ?></h1>
<?php
	$attachments = get_children(array(
		'post_parent' => get_the_ID(),
		'post_type' => 'attachment',
		'post_mime_type' => 'image',
		'order' => 'DESC'
	));

	if(!empty($attachments)){
		$img = array_shift($attachments);
		echo wp_get_attachment_image($img->ID ,'large') ;
	}
?>

単純にタイトルと画像のみを表示するようにした。
他にも表示したいものができたら、都度追加すればよい。

投稿する際の注意としては、1投稿に複数の写真を載せても、一つしか表示されない。
あとは、必ず写真をフルサイズで投稿すること
写真をダウンロードする際に必要になる。
フルサイズで投稿しても、表示するページでは上記のコードで自動的にlargeサイズにリサイズされる。

2.投稿した写真をサムネイルで一覧表示する

投稿の一覧ページは、通常「archive.php」が用いられる。
single.phpと同じように、「archive-photos.php」を用意すれば、カスタム投稿タイプ専用の一覧ページを作成しやすい。

archive.php

赤枠内を書き換え。

<ul class="photos_list clearfix">
<?php
/* カスタム投稿タイプを表示する */
$loop = new WP_Query( array( 'post_type' => 'photos', 'posts_per_page' => -1 ) );
while ( $loop->have_posts() ) : $loop->the_post();
?>
	<li>
	<?php				
		$attachments = get_children(array(
			'post_parent' => get_the_ID(),
			'post_type' => 'attachment',
			'post_mime_type' => 'image',
			'order' => 'DESC'
			));

		if(!empty($attachments)){
			$link = '<a href="<?php the_permalink() ?>">';
			$img = array_shift($attachments);
			echo '<a href="';
			the_permalink() ;
			echo '">';
			echo wp_get_attachment_image($img->ID ,'thumbnail') ;
			echo '</a>';
        }
	?>
	</li>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
</ul>

これで、ドメイン名/photos/ にアクセスすれば、アップした写真がサムネイルで表示される。
サムネイルの表示はスタイルシートで整える。
サムネイルをクリックすると、先に作成したsingle-photos.phpに移動できる。

3.サムネイルをクリックすると、ダウンロードページに行く

サムネイルをクリックすると、single-photos.phpに移動して、投稿した写真が表示されている。
ここに、ダウンロード機能をつければよい。
のだが、「右クリックで保存」が嫌だったので、リンクをクリックでローカルディスクに保存できるようにしてみた。

正直、ここはあまり綺麗に作れなかった。
将来的には改善したい。

固定ページで、ダウンロード機能を動かすページを準備し、パラメータでダウンロードしたいデータを渡す仕組み。
うーん、もっといい方法ないかなあ・・・。

まず、DL専用ページの準備。
page.phpをもとに、imageDL.phpを作成。
全然元にしてないけど。
「それぞれのドメイン」の部分は、各サイトに合わせて置き換え。

<?php
/*
Template Name: page_imgeDL
*/
?>
<?php
$imge_src = $_GET['img_url'];
if (!empty($imge_src)){
	$imge_src = htmlspecialchars(trim($imge_src));
}else{
	echo '<p>指定されたファイルは存在しません</p>';
	exit();
}

$imge_src = str_replace('http://それぞれのドメイン', './', $imge_src);

if (!file_exists($imge_src)) {
  	echo '<p>指定されたファイルは存在しません</p>';
	exit();
}

$fname = basename($imge_src);

//画像のダウンロード
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($imge_src));
header('Content-disposition: attachment; filename="' . $fname . '"');
readfile($imge_src);
exit();
?>

ファイルをアップしたら、固定ページから作成したファイルをテンプレートに指定して、ダウンロード機能専用ページを作成。
Template Name: page_imgeDLとしているので、page_imgeDLをテンプレートに指定。
このページは実際表示されることはない。
パーマリンクの指定に注意。
imageDL

注意点としては、作成した固定ページがサイトのメニューに載ってしまうため、表示されないようにいじらなければならないこと。

やっぱりあまりいい方法ではないな。
機会があったら他のやり方を考えないと。

次に、single-photos.phpに、今作成したダウンロードページへのリンクを設ける。
先ほどカスタマイズした部分に、下の2行を追加。

<h1><?php the_title(); ?></h1>
<?php
	$attachments = get_children(array(
		'post_parent' => get_the_ID(),
		'post_type' => 'attachment',
		'post_mime_type' => 'image',
		'order' => 'DESC'
	));

	if(!empty($attachments)){
		$img = array_shift($attachments);
		echo wp_get_attachment_image($img->ID ,'large') ;
	}
?>

<?php $image_path = wp_get_attachment_image_src($img->ID , 'none', true);?>					
<p><a href="/imagedl/?img_url=<?php echo $image_path[0] ?>" target="_blank">写真をダウンロード</a></p>

リンク先は、ダウンロード機能専用に指定したページのパーマリンクに合わせる。
これで実際写真をアップしたら、一通り完成する。

今回は正直、最低限の機能の実装のみ。
また時間を設けて改良しよう。

次回の改良予定。

・写真のカテゴリ分け
・ダウンロード機能の改良
・ページネーションの実装

投稿のカテゴリ スラッグとファイル名

WordPressでカスタム投稿タイプを作成すると、作成したpost typeにあわせた名前で個々のファイルをカスタマイズできる。
例えば、newsというpost typeを作成したら

single-news.php
archive-news.php

など、作成したpost type専用に内容をカスタマイズ可能。

ふと気になって、カスタム投稿タイプを作らなくても、投稿のカテゴリのスラッグで同じようなことできないかな、と思い試してみた。

というのも、投稿のカテゴリのトップページに、個々にアレンジを加えたかったから。
if使ってもいいのだけど、ゴチャゴチャするので。

できた。

例えば、sportsってスラッグのカテゴリを作った場合、

category-sports.php

で、専用にアレンジできるページになる。

追記

category-sports.php
ではなく、
taxonomy-sports.php
にする。