カテゴリー
ブログ

「最近読まれているリスト」機能の実装

概要

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

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

詳細

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

WordPress は投稿ごとのアクセス数を記録しないので、count_history というカスタムフィールドを作り、直近1週間のアクセス数を保存する。たとえば今日が 2021/6/2 とすると次のような配列として保存される。

Array
(
    [20210602] => 10
    [20210601] => 20
    [20210531] => 30
    [20210530] => 40
    [20210529] => 30
    [20210528] => 20
    [20210527] => 10
)

具体的な処理は次の通り。

$count_history = get_post_meta( get_the_ID(), 'count_history', true );

// アクセス履歴あり
if( is_array($count_history) ) {
	// 7日前の日付を取得
	$expdate = (int)wp_date( 'Ymd', strtotime( '-7 days' ) );
	foreach( $count_history as $date => $count ){
		// 7日前以前の記録を削除
		if( $date <= $expdate ) {
			unset( $count_history[$date] );
		}
	}
// アクセス履歴なし
} else {
	$count_history = array();
}

// カウンタをインクリメント
$today = (int)wp_date( 'Ymd' );
( isset($count_history[$today]) ) ? $count_history[$today]++ : $count_history[$today] = 1;

update_post_meta( get_the_ID(), 'count_history', $count_history );

カウント処理は、ページ表示処理の終盤に低い優先度で実行させる。

add_action( 'wp_footer', 'kh_access_count', 30 );
function kh_access_count() {

	/*
	 * カウントしないアクセスを識別する
	 */
	// 投稿でないか 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 '';
	}

	/*
	 * カウントする
	 */
	(上述の処理)

	return '';
}

ウィジェットを実装する

  • 表示させるだけのウィジェットなので、form() と update() はつくらない。
  • 表示内容は キャッシュし、1時間ごとに更新。
/*
 * アクセスカウンターウィジェット
 */
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' );
});

コメントを残す

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