非插件实现分类目录的自定义排序,看上去是个很小的功能,但是改动起来花了大量时间;
如果是插件方式实现,可以用这个:Category Order and Taxonomy Terms Order,具体实现方案中的代码也是从这个插件中抠出来的:
基本思路就是在数据库中添加排序字段,然后在取值 sql 语句中按照这个字段排序,或者先取出数据再根据这个字段排序;
1、数据库中添加排序字段
需要修改的表名称为 wp_terms,wp_ 是自定义表前缀,根据自己的实际情况查找;
执行下面的 sql 语句,就在 wp_terms 中添加了一个名为 term_order 的字段,tinyint 类型,默认是0;
alter table wp_terms add term_order TINYINT default 0
1. 字段的数据类型用了 tinyint ,取值范围0-255,对我来说,分类目录不会创建太多,这个范围足够了;如果不放心,可以用 int 或 integer 类型;
2. 自定义表前缀可以在根目录下的 wp-config 中找到,这个变量 $table_prefix;
3. wp-terms 表主要是用来放分类目录、标签等数据的,修改顺序的时候只修改分类目录相关的数据就可以;
添加完字段后,就可以修改顺序了,需要显示在上/前面的值要小一些;
2、修改取出数据的顺序
2.1 wp-includes/widgets/class-wp-widget-categories.php 中,排序字段由默认的 name 改为 term_order;
public function widget($args, $instance){
// …… 省略的其他代码
if ($title) {
echo $args['before_title'] . $title . $args['after_title'];
}
$cat_args = array(
'orderby' => 'term_order', // name 改为 term_order
'show_count' => $c,
'hierarchical' => $h,
);
echo 'orderby-000';
// …… 省略的其他代码
}
此处的代码不能保证一定是只有目录组件用到了,但至少从现在的页面布局下,只有分类目录这个地方输出了 “orderby-000”;后台中的分类目录管理也没有输出;
2.2 在主题下的 functions.php 的最后,添加下面的代码:
function hook_get_terms_orderby($orderby, $args)
{
if ( apply_filters('to/get_terms_orderby/ignore', FALSE, $orderby, $args) )
return $orderby;
if (isset($args['orderby']) && $args['orderby'] == "term_order" && $orderby != "term_order")
return "t.term_order";
return $orderby;
}
add_filter('get_terms_orderby', 'hook_get_terms_orderby', 1, 2);
这段代码加了一个 get_term_orderby 过滤器,对 sql 语句中的 orderby 进行重置;
1. 这段代码的大体意思是如果 $args[‘orderby’] – 原始传过来的排序字段是 term_order,修改之后的 $orderby 不是 term_order,就重置为 t.term_order;相关逻辑可参考 “3、补充” 中的代码;
2. 第二步中修改的两个地方是相关的,2.1 中的字段和 2.2 中的 if 判断,是一个值,所以也可以把 term_order 替换成表中的其他字段或自定义字符串都可以,只要两边统一就好;只要保证过滤器最终返回 t.term_order 就可以;
排序功能到这里就完成了!!
3、补充
下面是修改过程中遇到的问题和思路,感兴趣的同学可以接着往下看;
一开始设想的是直接修改目录的排序字段,如果默认是按 name 进行排序,那我就改成新增的字段 term_order;只改一个地方就可以;实际操作中,找到了解决方案中的第2步,但是后边的逻辑又把这个字段覆盖了;下面是相关的的代码块:
1. wp-includes/class-wp-term-query.php
public function get_terms() {
// …… 其他代码
// 'term_order' is a legal sort order only when joining the relationship table.
$_orderby = $this->query_vars['orderby'];
echo $_orderby.'-000<br/>'; // term_order-000
if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
$_orderby = 'term_id';
}
echo $_orderby.'-111<br/>'; // term_id-111
$orderby = $this->parse_orderby( $_orderby );
echo $orderby.'-222'; // t.term_id-222 // 插件开启后值为 t.term_order-222
if ( $orderby ) {
$orderby = "ORDER BY $orderby";
}
// …… 其他代码
}
上面的输出值在注释中标明了,如1中,修改 orderby 字段为 term_order,但是传过来之后,又被改成了 term_id,注释说的是仅在 join 关系表查询的时候 term_order 才可用,不知道什么意思,这个方法也好多地方在用,应该是一个公共方法,不敢贸然改动;
2. 插件开启后,222的值又从 term_id 改回了 term_order,插件的改动肯定在 parse_orderby 函数中;
protected function parse_orderby($orderby_raw){
// …… 其他代码
echo $orderby . '-444<br/>'; // t.term_id-444
$orderby = apply_filters('get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy']);
echo $orderby . '-555<br/>';// t.term_id-555 //插件开启后为 t.term_order
// …… 其他代码
}
通过开关排序插件,最终是定位到了,原来是 get_terms_orderby 改回了值,对照 wp 文档查找 apply_filters 的意思,直接在插件代码中搜 get_terms_orderby,最终得到上面解决方案中第二步的代码;
排序插件大小不到100K,但实际上只有 添加数据库字段 和 添加过滤函数 这两个地方是有效的,其他的比如排序管理页面、配置文件、样式表、js等都是“无效”的;
对我来说,这种小功能这要装个插件是不能接受的,后续还会有其他需求,如果一个小改动就要引一个插件,项目资源会越来越多,系统会越来越慢,所以修改的思想是能自己改的就自己改,逻辑太过复杂和工作量太大的,有对应的插件就直接用;
当然如果为了节省时间,用插件完全没问题;