WordPress 无缝改进置顶方案

WordPress 置顶机制

首先我们观察一下 WordPress 的文章置顶机制。

只有在文章的“快速编辑”下面有一个钩钩“置顶文章”。

然后我们发现置顶之后好像也没有改变什么。

也许我们以为,只是以为。置顶应该是文章的一个属性。

可惜不是,wordpress 竟然是用一个叫做 sticky_posts 的选项,存下了所有置顶文章的编号。

这就算了。

因此,我们想要置顶文章真的置顶起来(排序的时候排到前面),而且不改变其他的查询结构(分类、搜索、分页等等),按照原来这套难上加难。

一般的解决方案

首先,有一个函数 is_sticky($post_ID) 检查一个文章是否置顶:

http://codex.wordpress.org.cn/Function_Reference/is_sticky

然后,在文章查询参数里面可以指定 ignore_sticky_posts 参数为 1 来忽略掉指定的文章。

http://codex.wordpress.org.cn/Wp_query

于是,一般在网上能够查找到的方法(参见上面的链接页面,查询 [Show Sticky Posts] 关键字),都是手动改查询,先用 'post__in' => get_option('sticky_posts') 查一遍有置顶的,再用 'ignore_sticky_posts' => 1 来忽略置顶,获取剩下的:

$sticky = get_option( 'sticky_posts' );
$query = new WP_Query( 'p=' . $sticky[0] );

$args = array(
    'posts_per_page' => 1,
    'post__in'  => get_option( 'sticky_posts' ),
    'ignore_sticky_posts' => 1
);
$query = new WP_Query( $args );

$sticky = get_option( 'sticky_posts' );
$args = array(
    'posts_per_page' => 1,
    'post__in'  => $sticky,
    'ignore_sticky_posts' => 1
);
$query = new WP_Query( $args );
if ( $sticky[0] ) {
    // insert here your stuff...
}

这样多不优雅,还亏是官方的解决方案。分页怎么办,一个变两个,默认的循环怎么兼容?!

我自然是不满足于这种方案,因此自创神招,请看下文。


Solution

我的构想

其实中途走了弯路,但是目标是明确的。

我想,例如在 category.php 或者 search.php 里面,不都有一个默认的循环吗?

难道不应该直接改变默认的循环,直接让置顶排在前面就好了?

我又想,这个置顶的存放位置这么奇葩,难道置顶不应该是文章的属性吗?

我又想了,如果文章有一个属性指明了是否置顶,我按其排序不就行了?的确可以啊!

那我何不自己根据 sticky_posts 的选项自己维护一个文章的置顶属性?!

OK!那就这么干了!

第一步:维护文章的置顶属性

由于如果按照 post_meta 排序,必须保证所有文章都有这个 post_meta。

我现在假定所有文章都有一个自定义属性 is_sticky,取值为 0 或者 1。

那么为了维护这个,我可以在两个位置触发:

  1. sticky_posts 选项改变时(这会在“快速编辑”修改了“置顶文章”选项保存的时候被触发),遍历所有文章,将 sticky_posts 的文章的 is_sticky 设置为 1,其他的设置为 0;
  2. 当一篇文章被保存之后(可能是新增),用 is_sticky($post_ID) 查一下它是否置顶,并按此设置它的 is_sticky 值。

只要两步都达到,那么就可以保证文章的 is_sticky 的 meta 值可以代表置顶状态。

先放上这两段的实现:

/**
 * @param $id
 * 更新文章的时候根据置顶设置设定 is_sticky 值
 */
function set_stick_after_post_update($id) {
    update_post_meta($id, 'is_sticky', in_array($id, get_option( 'sticky_posts' )) ? 1 : 0);
}
add_action('post_updated', 'set_stick_after_post_update');

/**
 * @param $option
 * 更新置顶状态的时候刷新所有的 is_sticky 属性
 */
function update_sticky_posts($option) {
    if($option == 'sticky_posts') {
        $posts = get_posts(array('posts_per_page' => -1, 'post_status' => 'any'));
        foreach($posts as $post) {
            update_post_meta($post->ID, 'is_sticky', is_sticky($post->ID) ? 1 : 0);
        }
    }
}
add_action('updated_option', 'update_sticky_posts');

OK,大功告成,然后我们进入下一个环节。

第二步,修改默认的查询循环,优先按照文章的 is_sticky meta 值进行排序

首先我们发现有一个钩子 pre_get_posts 可以修改默认循环的查询参数 $query。

那么我们设置 'meta_key' => 'is_sticky' 以及 'orderby' => array('meta_value_num' => 'DESC', 'post_date' => 'DESC')

来设置先按照 is_sticky 这个 meta_key 排序,然后再按照时间排序。

/**
 * @param $query
 * 根据 is_sticky 的字段来进行排序查询
 */
function sort_by_sticky( $query ){
    if ( $query->query['post_type'] == 'post' && ( $query->is_home() || $query->is_archive() ) && $query->is_main_query() ) {
        $query->set( 'posts_per_page', 5 );
        $query->set( 'meta_key', 'is_sticky' );
        $query->set( 'orderby', array('meta_value_num' => 'DESC', 'post_date' => 'DESC'));
        $query->set( 'order', 'DESC' );
    }
}
add_action( 'pre_get_posts', 'sort_by_sticky' );

于是默认循环就如我们所愿改变了,一切就好像没有发生过一样,无缝插入。

太优美了。轻轻挂了三个钩子解决所有问题。


【转载请附】愿以此功德,回向 >>

原文链接:https://www.huangwenchao.com.cn/2015/04/wordpress-sticky.html【WordPress 无缝改进置顶方案】

《WordPress 无缝改进置顶方案》有3个想法

  1. 新版的wordpress改变了置顶属性的设置顺序,目前最新的是这样:更新完post数据,do_action”post_updated”,然后再修改置顶属性,这样会导致,当在快速编辑面板修改置顶复选框时,会出现相反的结果,直到下次再修改了其他属性保存的时候变正常

发表评论

电子邮件地址不会被公开。 必填项已用*标注