WP笔记

WordPress——访客选择文章排序的实现

WordPress内建的排序方式有很多,比如按照标题排序、按评论数多少排序或者按照某个自定义字段的值排序。如果可以让访客自由选择网站文章的排序方式,可以帮助访客发现他们想要的资源,提高用户黏度。本文探讨如何实现访客选择文章排序方式,包括按照浏览次数和用户打分排序。

目标

要实现的功能包括

  • 用模板标签添加文章排序菜单,也就是用户能看到的前台部分
  • 在header.php中添加模板标签,在首页和所有分类页面显示排序链接,支持对首页和所有分类页面排序,包括category、tag、author archive、 date archive等。
  • 按照评论多少排序,这个只需要修改orderby参数,其它排序方式包括title、date、rand等等与之相同,所以选择评论数(comment_count)作为代表
  • 按照浏览次数排序,其实就是按照自定义字段(custom field)排序,这里的自定义字段叫做views,也许你能猜到,要借助WP-PostViews插件
  • 按照用户打分(Ratings)排序,同样是自定义字段排序的例子,借助WP-PostRatings插件

开始之前

1. WP PostViews插件自带排序功能,所以无需重新写这部分代码,详情参阅《WP-PostViews浏览次数统计插件使用详解

2. WP PostRatings也自带排序功能,使用的参数是r_sortby,WP PostViews使用的是v_sortby,要求参数统一的情况下只能取其一,就选WP PostViews的参数,正好练习一下如何利用custom field排序的方法

3. 排序在query_post执行之前完成,也就是说我们不用去模板文件里写query_post()或者new WP_Query,而是在主循环输出之前就改变排序顺序,这样可以减少数据库查询次数,这也是WP PostViews的做法。

4. 将代码封装在Class中,我们的代码看起来是这样

class Sola_Sort_Posts {
   //代码
}

使用Class封装可以有效避免全局变量的使用,减少与其它插件冲突的机会,只要Class的名字不重复就没事。

声明Class,写构造函数

class Sola_Sort_Posts {
	
	private $sort_views;
	private $sort_ratings;
	
	function __construct() {
		
		If (in_array( 'wp-postviews/wp-postviews.php',apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) )
			$this->sort_views = true;
			
		If (in_array( 'wp-postratings/wp-postratings.php',apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) )
			$this->sort_ratings = true;
			
		add_action('pre_get_posts', array(&$this, 'posts_sorting') );
		
		add_filter('query_vars', array(&$this, 'add_sorting_vars') );

	}
	

构造函数前两句检测与该插件有依赖关系的两个插件是否启用,如果没启用,后面就不会显示按照浏览次数和打分的链接。

按照评分(Ratings)排序

这部分的实现代码如下

function posts_sorting($local_wp_query) {
	$sortby = $local_wp_query->get('v_sortby');
	$is_valid = is_home() || is_archive();

	if( $sortby && $is_valid ) {	
		if( $this->sort_ratings && $sortby == 'ratings') {
			add_filter( 'posts_fields', array(&$this, 'ratings_fields') );
			add_filter( 'posts_join', array(&$this, 'ratings_join') );
			add_filter( 'posts_where', array(&$this, 'ratings_where') );
			add_filter( 'posts_orderby', array(&$this, 'ratings_orderby') );
		} else if( $sortby == 'comment_count' ) {
			add_filter( 'posts_orderby', array(&$this, 'comment_count_orderby') );				
		}			
	}
}

这部分代码将在action: pre_get_posts处执行,排序只能用于首页或者archive(分类、tag、作者archive、日期archive等)页面,所以检测一下是否在这些页面,如果不是就不要执行。

检测一下当前的v_sortby的值,如果地址是https://www.solagirl.net?v_sortby=ratings,就是按照ratings排序,这时添加四个filters,修改主循环SQL语句,使主循环查询时

  • SELECT时选出meta_value
  • 联合wp_postmeta表查询
  • 按照ratings_orderby排序,ratings_orderby是WP PostRatings插件记录平均值的数据库meta_key
  • 增加一个Where条件

下面是实现上述四点的具体函数

/** Function: Modify Default WordPress Listing To Make It Sorted By Post Ratings */
function ratings_fields($content) {
	global $wpdb;
	$content .= ", ($wpdb->postmeta.meta_value+0) AS ratings_average";
	return $content;
}
function ratings_join($content) {
	global $wpdb;
	$content .= " LEFT JOIN $wpdb->postmeta ON $wpdb->postmeta.post_id = $wpdb->posts.ID";
	return $content;
}
function ratings_where($content) {
	global $wpdb;
	$content .= " AND $wpdb->postmeta.meta_key = 'ratings_average'";
	return $content;
}
function ratings_orderby($content) {
	$orderby = trim(addslashes(get_query_var('v_orderby')));
	if(empty($orderby) || ($orderby != 'asc' && $orderby != 'desc')) {
		$orderby = 'desc';
	}
	$content = " ratings_average $orderby";
	return $content;
}

实现按照comment_count的方式就是修改一下orderby参数,不在赘述。

按照ratings排序的主循环查询语句将会是这样

SELECT SQL_CALC_FOUND_ROWS wp_posts . * , (
wp_postmeta.meta_value +0
) AS ratings_average
FROM wp_posts
LEFT JOIN wp_postmeta ON wp_postmeta.post_id = wp_posts.ID
WHERE 1 =1
AND wp_posts.post_type = 'post'
AND (
wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private'
)
AND wp_postmeta.meta_key = 'ratings_average'
ORDER BY ratings_average DESC
LIMIT 0 , 10

在模板中添加模板标签实现

在类中定义一个方法输出我们需要的排序链接

function print_menu() {
   ...
}

在类结束后,实例化这个类,定义一个新的函数作为模板标签

/** Instance the class */
$sort_posts = new Sola_Sort_Posts();	

/** Function: Template tags for displaying the sorting menu */
function sort_posts() {
	global $sort_posts;
	$sort_posts->print_menu();
}

这样用户只需要在模板中(例如header.php)添加函数sola_sort_posts()就可以在首页和所有分类页面显示排序链接,以twentyeleven为例,

//twentyeleven的header.php最后
<div id="main">
	<?php if( function_exists('sola_sort_posts') ) sola_sort_posts(); ?>

效果

排序菜单

最终效果展示

按照评论排序

orderby comment

按照Ratings排序

orderby ratings

控制样式

可以在主题的style.css中为排序表单添加样式,例如让表单横向排列,选中的以红色下划线字体显示

ul.post-sort li { 
	float:left;
	margin-right:20px;
}
ul.post-sort .current-sort a {
	color:#FF0000;
	text-decoration:underline;
}

插件下载

源代码以插件形式展现

[download id=37]

将sort-posts.php放在plugins目录下,后台激活插件,在header.php中添加如下代码即可使用

<?php if( function_exists('sola_sort_posts') ) sola_sort_posts(); ?>

扩展

在这段代码基础上,还可以扩展更多排序方式,包括

  • 按照字母数顺序排序(post_name)
  • 按照作者名字排序(post_author)
  • 按照发布时间排序(post_date),这是默认的排序方式
  • 按照文章修改时间排序(post_modified)
  • 按照随机顺序排序(RAND())
  • 按照贝叶斯平均排序,也就是在按照Ratings排序时考虑打分人数,参考文章《基于贝叶斯平均的产品排序方法

注意:这里使用的orderby参数与WP Query文档中给出的参数不同,因为WP Query的排序是经过封装的,我们用的方法是直接修改SQL语句,所以要以数据库字段的实际名称为准。

参考文章

WP Query Order Parameters

WP Query Filters

29条评论

  1. 同样的主题,启用插件后以前可以正常显示,现在显示不了,是不是WordPress版本升级了,插件不支持了呢

  2. 您好,本身剛接觸wordpress沒多久..由於專案需要
    需要做到在首頁就以ratinig來排序,並且在各分類中(or tag)達到分類的rating排序
    原本是用rating widget但發現他好像做不到這樣的事情..
    搜尋很久終於看到這篇好像看到救星一樣!! 於是改裝了wp-postrating @@

    想請教幾個蠢問題

    1. 聲明class Sola_Sort_Posts & 按照評分排序 function posts_sorting 這部分是放在function.php裡面媽??
    2. 4個有關下sql語法的程式碼要放在哪裡,也是function.php裡面媽??
    3. 在模板中添加模板標籤實現 => 定義一個function print_menu() {這function裡面的內容是..??} & 定義一個新的函數作為模板標籤 ~ 這邊的程式碼是要放置何處 ,,

    最後再自己想要的地方使用 只有這邊看得懂….@@@@

    可能是我太笨了看不懂這樣詳細的教學文..問題很多..希望能解決我現在遇到的問題..謝謝!!

    1. 在插件下载section提供了下载链接,是插件形式,用上传方法安装到wordpress里,激活后在主题模版里添加函数即可

      这文章念头很久,代码也没更新过,我看了下有很多问题,虽然能排序,但如果文章没有ratings字段就直接不显示了。

      这个问题的本质是按照custom field排序,你可以搜索相关文章。wp postratings插件负责给每篇文章添加平均分这个custom field(需要用户打分后才会产生),你需要用这个custom field做排序。

      1. 嘿~我能用了~~~
        另外想請問一下
        我有沒有辦法在wordpress預設首頁裡就直接帶參數來做這個效果呢

        1. 非常感謝!!!
          我已經成功囉

          但是我另外想要詢問一些問題..
          後台中可顯示評分過的相關資訊log(每一筆誰給誰評分..等等)
          我想要做到一個使用者評分排行榜(評分最多次的user)在側邊欄
          感覺似乎是做的到的..我要怎麼做呢
          甚至是顯示使用者評分過的對象..有相關資訊媽..要怎麼做呢??

  3. 博主,你好,
    我自定义了一个“post_type”,这种情况下应该怎么修改这个插件呢
    谢谢

    1. 你试试在pre_get_post那里设定一下post type,用$query->set( ‘post_type’, ‘your_type’ );的方式。我没试过,你试试看。至于wp post views自带的排序,你要查查资料或读一下源代码,看它是不是支持custom post type

  4. 博主你好,访客文章排序(按自定义字段排序)可否做成下拉菜单形式,排序功能不要在链接里显示出来,这段代码可以实现 http://pastebin.com/LCFS945E 这个功能,但是无法分页,点击第2页或其它页后排序就无用了,而这段代码 https://gist.github.com/stompweb/3018263 可以实现无链接形式,但又不能以访客的方式选择,还是想用第一种方法结合 pre_get_posts 来做,博主有什么办法解决么?非常感谢!

    1. 分页以后排序无用是因为表单的值无法传递到分页后的页面,这个似乎用第一种或第二种都解决不了。
      用session记录表单的选择,可以传递到分页后的页面。
      将表单提交的数据变成query string的一部分,也可以在其它页面获取表单选择。
      我感觉这个传值问题解决了,别的就好办了。

      按你说的用第一种方法加pre_get_posts来实现,我觉得过程是这样:
      用户选择排序顺序后,保单提交,页面重新加载,首先会执行pre_get_posts hook,将你第一段代码中处理表单提交数据的那段逻辑用在pre_get_posts那个函数中,用$query->set修改query string。
      用自定义字段排序要使用meta query,参照第二段代码。

      个人愚见,还望多多指教。

  5. WP PostRatings 这插件很早的时侯也用过,感觉很占数据库体积,因为它还纪录打分的IP地址,不知道现在是不是还是这样的?

    1. 这种程序不大可能不占数据库,除非降低准确性。如果网站流量大,就应该交给第三方程序去完成,基于这个原因,WordPress一款很出色的插件Popularity Contest已经停止更新,而且不推荐使用了http://wordpress.org/extend/plugins/popularity-contest/

  6. 能问你一个问题吗?关于你在窦露博客留言中回复到的WP Favorite Posts 插件,当使用了你推荐的插件,在安装过程中好像就出现了问题,插件说是在新建页面中只要插入{{wp-favorite-posts}}即可,但并没有用,请问你知道是什么原因吗?

    1. 我刚在wp3.4试了一下,可以用啊。在page编辑页面插入{{wp-favorite-posts}},这个页面就会显示你收藏的文章,前提是你得先收藏几篇文章。你可以看看这个博客www.tiger-blog.com,他每篇文章标题下面有个链接叫添加到我的收藏夹,页面顶部的链接有“我的收藏夹”,这就是wp favorite posts的实际效果,我的收藏夹就是添加{{wp-favorite-posts}}的页面。

      如果不能用,你就装在裸的wordpress上试试,可能是你网站的什么插件跟他冲突了,另外不要禁用cookie。

      1. 貌似贵站的评论时间有点问题,1楼的时间是12:36,而2楼回复的时间确实9:57

        1. Ludou果然好眼力。
          他的回复时间实际上是凌晨(00:36),我输出时间用的WordPress默认函数,后台设置的时间格式是a g:i这种,是WordPress默认选项里的第一个,貌似g的有效区域是1-12,所以00点会自动转换成12点,就出现这种奇怪的现象了。而且WordPress提供的选项都是带g的,就都存在这个问题。
          我换成自定义的格式H:i,应该不会引起这种误会了。
          万分感谢,又让我发现个以前没注意到的问题!

      2. 你好 很感谢您的回复,听了您的建议我。我采取了以下动作:
        1、我进了你推荐的www.tiger-blog.com博客后收藏了几篇文章,然后就进“我的收藏夹”页面,但是里面就默认的写收藏夹已清空。用的是360浏览器也没设置自动清空cookie,不太清楚是什么原因。

        2、我只装有3个插件,听了你的建议,我停用了那3个插件,然后再试,还是无法显示我收藏的文章。我的操作如下:
        新建一个叫Favorite.php的文件,然后在后台“页面”-新建页面–选择Favorite page模板页。

        Favorite page
        模板页 内容如下:

        {{wp-favorite-posts}}

        但是当我点击”查看“此网页时,他就直接输出文本”{{wp-favorite-posts}}“的字样,就没有任何其他东西了

        1. {{wp-favorite-posts}}直接输出说明你的wp没能解析这个shortcode,在网上看确实有人反映这个软件会和某些插件冲突导致不能用,tiger的以前能用的,最近不知道为啥不能用了。

          不过我本地安装的wp,用默认主题,可以用。请问你的favorite.php文件怎么写的?建议你用默认主题的默认page模板试一下,如果你的模板文件代码有问题,也可能导致错误发生。

        2. 经过你的提示,我重新,新装的裸wordpress测试,能够正常收藏和取消收藏,但是在列出收藏页面时,就还是直接输出函数的字样,证明应该是我在新建页面插入{{wp-favorite-posts}}代码上有误。

          您可以把你从怎样创建新页面到插入{{wp-favorite-posts}}这句代码的操作及代码的详细文件给我讲下吗? 如果真的从您的博客这得到了帮助并解决了问题,那就十分感谢了!

        3. 你说的情况,除了你加的shortcode有错误之外,我想象不出别的原因。所以我装了一个测试站,安装了wp favorite posts插件,收藏功能和收藏夹页面我都加好了,你可以看一下。如果想看后台怎么加的,请你自己注册一个账户,就能看到收藏夹页面是怎么编辑的了。
          测试站地址 http://demo.solagirl.net/

        4. 兄弟啊,咱能不用qq邮箱不,俺这是国外服务器哦。

          密码设置成你注册时用的qq号了,自己试试吧。

      1. 请问下 博客回复 是怎么实现的?如果是博客,就增加单独的样式吗
        博主回复

评论已关闭。