カテゴリー
ブログ

「最近読まれているリスト」ウィジェット

概要

最近アクセスの多いリストを一覧表示するウィジェットを実装する。

人気のあるリストを紹介する場合、総アクセス数で測ると表示されるリストが固定的になるうえ、露出が高まることでアクセス数が増えるのでアクセス数の差が助長される。逆に直近の24時間など短い時間のアクセス数では変動が大きく、人気のあるリストとは言い難くなる場合もある。そこで直近の1週間程度のアクセス数の多いリストを表示させることにした。

詳細

最近アクセスの多いリストを記録する

  • WordPress は投稿ごとのアクセス数を記録していないので、データを一時保持する Transient に持たせる。
  • Transient 名を「識別子+投稿 ID」として一意にし、アクセス数を記録していく。
  • 毎週土曜の0時にキャッシュをクリアすることで、簡易的に「最近(=ここ1週間のうちに)」アクセス数の多いリストを取り出せる。
  • ページ表示処理の終盤に低い優先度で実行させる。
add_action( 'wp_footer', function() {

	/*
	 * カウントしないアクセスを識別する
	 */
	// 投稿でないか list カテゴリに属していなければリターン
	if( ! is_single() || ! has_category( 'list' ) )
		return '';

	// 自分のアクセスはカウントしない
	if( current_user_can( 'administrator' ) )
		return '';

	// botのアクセスはカウントしない
	$ua = $_SERVER['HTTP_USER_AGENT'];
	$bots = array( 'googlebot', 'msnbot', 'yahoo' );
	foreach( $bots as $bot ) {
		if( false !== stripos( $ua, $bot ) )
      		return '';
	}

	/*
	 * カウントする
	 */
	$post=get_post();

	// 現在のカウンター数値を取得
	$count = get_transient( 'READCOUNT_'.$post->ID );

	// 取得できればインクリメント、できなければ 1
	$count = ( false !== $count ) ? ++$count : 1;

	// カウンターをセット(保持期間は次の土曜0時まで)
	set_transient( 'READCOUNT_'.$post->ID, $count, strtotime('next saturday')-strtotime('now') );

	return '';
}, 30 );

ウィジェットを実装する

  • 表示させるだけのウィジェットなので、form() と update() はつくらない。
  • 表示内容は キャッシュし、1時間ごとに更新。
  • カウントを格納する option_value フィールドは文字列なので、カウント数の大きい順にソートするために SQL 文の CAST() が必要。
/*
 * アクセスカウンターウィジェット
 */
class KHReadCountWidget extends WP_Widget {

	/**
	 * ウィジェット名などを設定
	 */
	public function __construct() {
		parent::__construct(
			'khreadcount', // Base ID for the widget, lowercase and unique. 
			'よく読まれたリスト', // Name
			array( 'description' => 'ここ1週間でよく読まれたリスト', ) // Args
		);
	}

	/**
	 * ウィジェットの内容を出力
	 */
	public function widget( $args, $instance ) {

		// キャッシュがあればそれを出力して終了
		if ( false !== $out = get_transient( 'POPULARLISTS' ) ) {
			echo $out;
			return;
		}

		global $wpdb;

		// アクセスカウントを多い順に10取得
		// 変数がないので $wpdb->prepare を使う必要なし https://bit.ly/2WxCqPs
		// option_valueフィールドは文字列なので数値にキャストしてソート https://dev.mysql.com/doc/refman/5.6/ja/cast-functions.html
		// アクセス数が等しい場合にはカウント期間の長い(先に期限切れとなる)リストを優先するため option_id ASC としている
		$sql = "SELECT option_name FROM wp_options WHERE option_name LIKE '_transient_READCOUNT_%' ORDER BY CAST(option_value AS SIGNED) DESC, option_id ASC LIMIT 10";
		if( null === $readcounts = $wpdb->get_col( $sql, 0 ) ) {
			$errmsg = "Class: ".__CLASS__."\nMethod: ".__METHOD__."\nLine: ".__LINE__;
			error_log( $errmsg, 1, get_option( 'admin_email' ) );
			return '';
		}

		// Widget への出力内容を作成。
		$out = $args['before_widget'].
			$args['before_title'].$args['widget_name']. $args['after_title'].
			'<ul>';
		$start = strlen( '_transient_READCOUNT_' );
		foreach( $readcounts as $readcount ) {
			// 投稿IDを取り出す
			$post_id = (int)substr( $readcount, $start );
			$out .= '<li><a href="'.get_permalink( $post_id ).'">'.get_the_title( $post_id )."</a></li>\n";
		}
		$out .= '</ul>'.$args['after_widget'];

		// 出力
		echo $out;

		// 保持時間1時間でキャッシュをセット
		set_transient( 'POPULARLISTS', $out, HOUR_IN_SECONDS );

	}

}

/*
 * ウィジェットの登録
 */
add_action( 'widgets_init', function(){
     register_widget( 'KHReadCountWidget' );
});

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です