目標
假設現在有一篇文章的分類包含以下多項,且彼此之間有階層關係。
- 關東
- 東京
- 代官山
- 澀谷
- 神奈川
- 橫濱
- 東京
所以我們想要將這些分類,依照原本的階層順序顯示為:關東、東京、代官山、澀谷、神奈川、橫濱。在 WordPress 中預設以name
排序:代官山、東京、橫濱、澀谷、神奈川、關東,而其他比較有意義的排序法大概也就是按文章數量count
(query查詢工具),但也並非想要的排列。
有些Plugin可以自訂分類排序,例如:
- WP Term Order
- Category Order and Taxonomy Terms Order
- Custom Taxonomy Order
但是能夠改的排序,限為同層分類的排序,例如你可以把自訂把「澀谷」擺到「代官山」前面,或者讓「神奈川」整個群組都比「東京」先出現,依然不是我們要的功能。
只好來實做看看。
取得所有分類
首先要取得某篇文章的所選分類,可以用get_the_category
來取得一個包含所有分類項目 Object 的 Array,再利用array_map
把這些分類的名稱單獨挑出來。
$object_id = get_the_ID();
$categories = get_the_category( $object_id );
$terms = implode( '、', array_map(function($item) { return $item->name; }, $categories ));
echo $terms; // 代官山、東京、橫濱、澀谷、神奈川、關東
或者,也可以使用wp_get_object_terms
來取得任意自訂分類。
$object_id = get_the_ID();
$taxonomies = 'category'; // 這邊我們就選擇預設分類
$args = array( 'fields' => 'all' ); // options arguments
$categories = wp_get_object_terms( $object_id, $taxonomies, $args );
$terms = implode( '、', array_map(function($item) { return $item->name; }, $categories ));
echo $terms; // 代官山、東京、橫濱、澀谷、神奈川、關東
我們發現:欸?階層不見了,最上層的「關東」表示憤怒竟然排在最後!!!先不要管為什麼,但接下來我們就要利用分類項目中的parent
屬性,來為每個項目進行認親的動作,以達到優先以階層排序的目的。
為分類找階層排序
懶得看文章的話程式碼在這裡,使用方法就是把wp_get_object_terms
改成get_object_terms_hierarchical
就可以惹。
<?php
/**
* Make terms retrieved by wp_get_object_terms() organized hierarchically.
*
* Use this function instead of wp_get_object_terms() to get a hierarchically
* structured array or term data.
*
* This function takes the terms returned by wp_get_object_terms() and organizes
* them into a hierarchical array with the parent's children (and grandchildren)
* stored in nested arrays (within the 'children' item).
*
* @see wp_get_object_terms() for parameters.
*
* @return array $terms Hierarchilized $terms array.
*/
function get_object_terms_hierarchical( $object_ids, $taxonomies, $args = array() ) {
$return_flat = true;
$tree = array();
$terms = wp_get_object_terms( $object_ids, $taxonomies, $args );
$ids = array_map(function($item) { return $item->term_id; }, $terms);
if ( ! empty( $terms ) ) {
foreach ( $terms as $term ) {
if ( $term->parent == 0 || !in_array($term->parent, $ids) ) {
if ( $return_flat ) {
array_push($tree, $term);
get_child_terms( $term->term_id, $terms, $tree );
} else {
$tree[ $term->term_id ] = $term;
$tree[ $term->term_id ]->children = get_child_terms( $term->term_id, $terms );
}
}
}
}
return $tree;
}
/**
* Organizes the child terms.
*
* This is a recursive function.
*
* @param interval $parent_id The parent ID to retrieve the children terms of.
* @param array $terms The term data.
*
* @return array Returns nested child terms.
*/
function get_child_terms( $parent_id, $terms, &$order_terms = null ) {
$children = array();
foreach ( $terms as $term ) {
if ( $term->parent == $parent_id ) {
if ( $order_terms ) {
array_push($order_terms, $term );
get_child_terms( $term->term_id, $terms, $order_terms );
} else {
$children[ $term->term_id ] = $term;
$children[ $term->term_id ]->children = get_child_terms( $term->term_id, $terms );
}
}
}
return $children;
}
第 17 行的$return_flat
設定輸出為單層 Array 或者維持階層,設定為false
的話子項目會存在children
這個 key 裡。
測試看看。
$object_id = get_the_ID();
$taxonomies = 'category'; // here we use the default
$args = array( 'fields' => 'all' ); // options arguments
$categories = wp_get_object_terms( $object_id, $taxonomies, $args );
$terms = implode( '、', array_map(function($item) { return $item->name; }, $categories ));
echo $terms; // 關東、東京、代官山、澀谷、神奈川、橫濱
關東終於拿下他該有的位置了,很好!我們搞定。
解釋一下
事實上在後台設定了分類間的階層關係,也只會在 WordPress 資料庫中新增一個parent
欄位描述某個分類的上層id,所以資料本身是沒有分層的,我們必須再自己重新配對好它們的從屬關係才行。
事實上,Wordpress 自己則是用了一個叫 Walker 類的方法,也一樣可以取得正確的階層分布,其基本原理也是會去「走」遍所有分類去做認親。
$object_id = get_the_ID();
$categories = get_the_category( $object_id );
$cat_ids = array_map( function($item) { return $item->term_id; }, $categories );
$args = array(
'include' => $cat_ids,// 我們只取文章有使用到的分類 id 們
'hierarchical' => true,
'title_li' => '', // 移除第一個大項目寫「分類」
'style' => '', // 設定成 list 以外的字串以避免輸出 ul>li
'separator' => '、' // 將預設的 <br /> 改為中文頓號
);
$terms = wp_list_categories( $args );
echo $terms; // 代官山、東京、橫濱、澀谷、神奈川、關東
成功!那你說我用wp_list_categories
就好啦?不行。
假如今天沒有選用任何最上層分類的話,這裡就會出現 bug。我們考慮同樣的分類階層,例如有一篇文章沒有勾選「關東」,只選用了其中的
- 神奈川
- 橫濱
這時候用 WordPress 的內建函式wp_list_categories
會得到
echo $terms; // 橫濱、神奈川
那欸阿餒?改用get_object_terms_hierarchical
看看。
echo $terms; // 神奈川、橫濱
成功啦!
參考資料
- EricBusch/get_object_terms_hierarchical
- WordPress/get_the_category
- WordPress/wp_get_object_terms
- WordPress/wp_list_categories