WordPress ではデフォルトの機能で、Sticky posts という記事を固定させる機能がある。しかし、それはカスタム投稿タイプには使えない。それを可能にするプラグインがあるが、ここでは自分で実装させる方法を紹介する。
Seamless Sticky Custom Post Types
Seamless Sticky Custom Post Types というプラグインで可能らしいのでコーディングに興味がない人はそちらを利用すればいいだろう。ただ、もしプラグインに頼りたくないという人は次に紹介する方法を利用してみるのも一つの手だ。
DIY
どういうメカニズムで機能を実装するかというと、カスタム投稿タイプに関連付けられたカスタムタクソノミーの指定したターム(言葉・タグ)が投稿に関連付けられた時に、それを固定記事(スティッキー)として扱うようにする。
なので、まず、カスタム投稿タイプとそれに関連付けが済んでいるカスタムタクソノミーが既に作成されていることを前提とする。まだの人はコーディングするか、 Custom Post Type UI などでも作れる。
用意するもの
準備するのは2つ
- カスタム投稿タイプのスラッグ
- カスタムタクソノミー (上の投稿タイプに関連付けられていること)のスラッグ
例えばここでは、note
カスタム投稿タイプと note_tag
カスタムタクソノミーとして話を進める。スラッグと名称は違うことは確かめて置きたい。例えば静的ページの名称は”ページ”だがプログラム内部でそれを識別するために使用されるスラッグは page
となっている。
用意できたら次のコードをテーマの functions.php
に放り込む。
PHP コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
class StickyTerms { /** * Stores supported post type slugs. * @var array */ public $aPostTypeSlugs = array(); /** * Specifies the taxonomy terms used to indicate sticky. * @var array */ public $aStickyTerms = array( // taxonomy slug => term names (not slug) // 'note_tag' => array( 'sticky' ), ); public $sByWhat = ''; public $bShowInPagedView = false; /** * Marks whether the callback routine in the `the_posts` is in the process * as it performs WP_Query which calls `the_posts` filter inside, nested calls occur. * @var bool */ private $___bProcessing = false; /** * Performs necessary set-ups. */ public function __construct( array $aPostTypeSlugs=array( 'note' ), array $aStickyTerms=array( 'note_tag' => array( 'sticky' ) ), $sByWhat='name', $bShowInPagedView=false ) { $this->aPostTypeSlugs = $aPostTypeSlugs; $this->aStickyTerms = $aStickyTerms; $this->sByWhat = $sByWhat; $this->bShowInPagedView = $bShowInPagedView; add_filter( 'the_posts', array( $this, 'replyToInjectStickyPosts' ), 10, 2 ); } /** * @param array $aPosts array of queried posts * @param WP_Query $oWPQuery * @return array */ public function replyToInjectStickyPosts( $aPosts, WP_Query $oWPQuery ) { if ( ! $this->___shouldProceed( $oWPQuery ) ) { return $aPosts; } $this->___bProcessing = true; // prevent nested calls $_aPostTypeSlugs = empty( $oWPQuery->query_vars[ 'post_type' ] ) ? array( 'post' ) : ( array ) $oWPQuery->query_vars[ 'post_type' ]; $_aStickyPosts = $this->___getStickyPostsWithID( $_aPostTypeSlugs ); // WP_Query[] $_aSortedPosts = array(); $_iInserted = 0; foreach( $aPosts as $_oPost ) { if ( ! array_key_exists( $_oPost->ID, $_aStickyPosts ) ) { $_aSortedPosts[] = $_oPost; continue; } // Insert it at the top array_unshift($_aSortedPosts, $_oPost ); $_iInserted++; unset( $_aStickyPosts[ $_oPost->ID ] ); } if ( empty( $_aStickyPosts ) ) { $this->___bProcessing = false; return $_aSortedPosts; } // Inject rest of the sticky posts array_splice( $_aSortedPosts, $_iInserted, 0, $_aStickyPosts ); $this->___bProcessing = false; return $_aSortedPosts; } /** * @return bool */ private function ___shouldProceed( WP_Query $oWPQuery ) { if ( $this->___bProcessing ) { return false; } $_aPostTypeSlug = isset( $oWPQuery->query_vars[ 'post_type' ] ) ? ( array ) $oWPQuery->query_vars[ 'post_type' ] : array( 'post' ); if ( empty( array_intersect( $_aPostTypeSlug, $this->aPostTypeSlugs ) ) ) { return false; } if ( ! is_main_query() ) { return false; } if ( ! is_post_type_archive() ) { return false; } if ( ! $this->bShowInPagedView && $oWPQuery->is_paged() ) { return false; } return true; } /** * @return array */ private function ___getStickyPostsWithID( array $aPostTypeSlugs ) { $_aResult = $this->___getStickyPosts( $aPostTypeSlugs ); $_aWithID = array(); foreach( $_aResult as $_oPost ) { $_aWithID[ $_oPost->ID ] = $_oPost; } return $_aWithID; } /** * @return WP_Post[] */ private function ___getStickyPosts( array $aPostTypeSlugs ) { $_sCallID = md5( serialize( array( $aPostTypeSlugs, $this->aStickyTerms, $this->sByWhat ) ) ); if ( isset( self::$___aCache[ $_sCallID ] ) ) { return self::$___aCache[ $_sCallID ]; } self::$___aCache[ $_sCallID ] = array(); // prevents unlimited nested calls. $_aTaxQuery = array( 'relation' => 'OR' ); foreach ( $this->aStickyTerms as $_sTaxonomySlug => $_aStickyTerms ) { // @see https://developer.wordpress.org/reference/classes/wp_query/#taxonomy-parameters $_aTaxQuery[] = array( 'taxonomy' => $_sTaxonomySlug, 'field' => $this->sByWhat, // 'term_id', 'name', 'slug' or 'term_taxonomy_id' 'terms' => $_aStickyTerms, // Where term_id of Term 1 is "1". 'include_children' => false, ); } $_aArguments = array( 'post_type' => $aPostTypeSlugs, 'numberposts' => -1, 'tax_query' => $_aTaxQuery, 'post_status' => 'publish', 'nopaging' => true, 'ignore_sticky_posts' => false, ); // @note This seems to produce 3 additional database queries in Query Monitor $_oWPQuery = new WP_Query; self::$___aCache[ $_sCallID ] = $_oWPQuery->query( $_aArguments ); return self::$___aCache[ $_sCallID ]; } static private $___aCache = array(); } new StickyTerms( array( 'note' ), array( 'note_tag' => array( 'sticky' ) ) ); |
使い方
使い方はクラスをインスタンス化するだけ。インスタンス時に、サポートする投稿タイプのスラッグと、タクソノミーのスラッグ、スティッキー扱いするためのタグ名を指定する。
- (array) 投稿タイプスラッグ
- (array) タクソノミースラッグ => ターム名1, ターム名2, ターム名3, …
- (string) タームのスラッグで調べるか名称で調べるか。default:
name
- (boolean) 2ページ目以降もスティッキー表示させるかどうか。 default:
false
上のコードでは何をやっているかというと the_posts
フィルターフックで、クエリ結果を編集し、指定した投稿タイプのクエリの場合、スティッキーのタームがついている投稿を取得して、存在する場合はその配列の前方に取得した投稿をインジェクトしている。
後は、上の例に従えば、sticky
というターム(タグ)を固定したい記事に付与すればOK。ケースインセンシティブなので、Sticky
でも構わない。ちなみに、このサイトの Notes のセクションはこの方法で表示させている。もし、pinned
というタームでも固定させたい場合、コンストラクターの 2つ目のパラメーターを次のようにしてあげる。
1 |
new StickyTerms( array( 'note' ), array( 'note_tag' => array( 'pinned', 'sticky' ) ) ); |
余談
今回は、途中説明を端折ってできるだけ短くまとめてみた。この方が執筆する方としても楽だ。一応上級のカテゴリーに入れておいたので、わかってる人向けの内容となっている。