WordPress过滤首页部分文章

我其实一直挺困扰《每周歌词》的展示问题。原本这个栏目是我高中时期为了做站点SEO,保证博客能按时更新设定的。所以这个系列一开始都更新的很潦草,甚至大部分是在返校路上写出来的,完全没有质量可言。但是现在我已经有充足的时间更新博客虽然我也不更新,所以也越来越重视《每周歌词》的质量。如今的《每周歌词》已经逐渐变成我个人对某首歌曲和它歌词的感悟了。但是原先存在着的大量《每周歌词》非常占用首页空间,让技术相关的文章都难以找寻,这就违背了这个博客的初衷了。我曾经也尝试了很多种办法以解决,比如单独开子博客(因为数据太难迁移放弃),还有写一篇专门用来推荐的文章索引(因为懒得更新放弃),但是这些办法都不尽如人意。

但是今天感觉找到了个不错的办法:在首页隐藏部分文章。目前的逻辑是:隐藏所有“每周歌词”分类的文章,但是显示最新一篇和标“推荐”的文章。为了实现这个操作,我做了多种不同的尝试。

尝试1:修改index.php

第一种尝试就是直接修改index.php遍历文章的部分,对符合条件的文章进行过滤。

缺点很明显:

  1. 没办法跨页判断第一篇歌词,如果要跨页判断需要引入额外的数据库查询
  2. 每一页的文章数可能不同
  3. 侵入式修改,需要更改主题的index.php逻辑

后两点是无论如何都无法接受的,因此只能考虑其他方法。

尝试2:pre_get_posts

经过一番查找,找到了一个可能有用的Hook点pre_get_posts。这个操作发生在构造查询对象后、解析查询对象(WP_Query)前,所以可以对查询对象进行修改。而且由于这是一个Hook,因此不会对主题代码有任何的侵入,相对可控。根据文档,找到了cat参数用来去除某个分类。

function kas_exclude_weeklyrics_from_home($query) {
      if ($query->is_home() ) {
          $query->set('cat', '-36');
      }
}
add_action('pre_get_posts', 'kas_exclude_weeklyrics_from_home');

但是这个方式的问题就更大了,因为我发现根本没办法完成需求。WP_Query的筛选条件之间都是“与”关系,根本没办法单独保留一篇文章。如果一定要用这个方法实现的话,就需要先进行一次查询找出所有非推荐、不是最后一篇的文章id,然后加入post__not_in参数来去除这些文章。但是这样来回转换数据的效率很差,而且拼接出的SQL也会很长。

尝试3:posts_clauses

理清了逻辑就会发现,如果既要实现功能又要保证效率,那查询的SQL其实相当复杂,所以修改SQL才是切实可行的方法。所以我去阅读了WP_Query的源码,发现在语句拼接结束后其实还有一些Hook点。

        /*
         * Apply filters on where and join prior to paging so that any
         * manipulations to them are reflected in the paging by day queries.
         */
        if ( ! $q['suppress_filters'] ) {
            /**
             * Filters the WHERE clause of the query.
             *
             * @since 1.5.0
             *
             * @param string   $where The WHERE clause of the query.
             * @param WP_Query $this  The WP_Query instance (passed by reference).
             */
            $where = apply_filters_ref_array( 'posts_where', array( $where, &$this ) );
 
            /**
             * Filters the JOIN clause of the query.
             *
             * @since 1.5.0
             *
             * @param string   $join The JOIN clause of the query.
             * @param WP_Query $this The WP_Query instance (passed by reference).
             */
            $join = apply_filters_ref_array( 'posts_join', array( $join, &$this ) );
        }

这些钩子的参数都是SQL的某个子句,因此直接把查询逻辑写在后面就行了。一开始我使用的是posts_where,但是后来发现如果要判断推荐日志就必须考虑postmeta表,所以还需要修改joingroupby子句。显然写三个filter修改有点傻,所以我找到了另一个Hook点posts_clauses,可以得到所有SQL子句。

function kas_only_newest_weeklyrics( $clause ) {
    if (is_home()) {	
		global $wpdb;
		
		// 属于每周歌词分类、最新的显示、推荐的显示
		$clause['groupby'] = "{$wpdb->posts}.ID";
		$clause['join'] .= " INNER JOIN {$wpdb->postmeta} ON ( {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id )";
        $clause['where'] .= " AND (" .
			"(ID NOT IN (SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = 36 AND object_id != (SELECT max(object_id) FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = 36)))" .
			" OR " .
			"( {$wpdb->postmeta}.meta_key = 'recommend_post' AND {$wpdb->postmeta}.meta_value = 'on' ))";
		
		// 就执行一次
		remove_filter('posts_clauses' ,'kas_only_newest_weeklyrics');
    }
    return $clause;
}
add_filter('posts_clauses' ,'kas_only_newest_weeklyrics');

最后删除filter防止影响到其他查询。不过虽然这样代价可接受、功能也可实现,但是直接修改SQL还是不甚完美,而且这样也假设了首页获取文章是第一个进行的查询操作。

后记

最后我选择了尝试3提到的方法,虽然还是有一些缺点,但是起码保证了功能和效率。同时也顺便用Code Snippets插件统一管理了现有的Patch代码,不用再改主题的function.php了。

不愧是我,这都能水一篇

Reference

  1. WP_Query文档:https://developer.wordpress.org/reference/classes/wp_query/
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片
  3. 没有图片
  4. 没有图片

评论

还没有评论。

在此评论中不能使用 HTML 标签。