ちょっと頼まれて作ってた WordPress を使ったWebサイトに記事別のアクセスランキングを載せる事になった。
最初はプラグインでやろうかと思ったがうまく動いてくれなかったしアクセスログ出すのにDB使ってたりしてちょっとなーって感じだったので、プラグインやめて自分で作る事にした。大したもんじゃないし。
それが以下のコード
~$ cat bin/make_ranking_html.php
<?php
ini_set('memory_limit', '64M');
require_once '/home/www/example.com/lib/ranking.php';
require_once '/home/www/example.com/htdocs/wp/wp-config.php';
date_default_timezone_set('Asia/Tokyo');
$date = date('Ymd', strtotime('yesterday'));
$log_file_path = RANKING_LOG_DIR . 'access_' . $date . '.log';
$log_data = file_get_contents($log_file_path);
$ret = array();
if ($log_data) {
$log_data_array = explode("\n", $log_data);
if ($log_data_array) {
foreach ($log_data_array as $val) {
$log = explode(',', $val);
if (!$log[0]) {
continue;
}
if (array_key_exists($log[0], $ret)) {
$ret[$log[0]] += 1;
} else {
$ret[$log[0]] = 1;
}
}
}
}
uasort($ret, function($a,$b) { if ($a==$b) { return 0; } return ($a > $b) ? -1 : 1 ; });
$mysql_connect = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
$mysql_select_db = mysql_select_db(DB_NAME, $mysql_connect);
$ranking_data = array();
$i = 0;
foreach ($ret as $id => $count) {
$result = mysql_query('SELECT * FROM wp_posts WHERE id = ' . $id);
$fetch = mysql_fetch_assoc($result);
if (!$fetch || $fetch['post_status'] != 'publish') {
continue;
}
$date = explode('-', $fetch['post_date']);
$ranking_data[$i]['no'] = $i + 1;
$ranking_data[$i]['id'] = $id;
$ranking_data[$i]['count'] = $count;
$ranking_data[$i]['url'] = '/archives/' . $fetch['ID'] . '/;
$ranking_data[$i]['title'] = $fetch['post_title'];
$i += 1;
if ($i >= RANKING_MAX) {
break;
}
}
$year = date('Y');
$month = date('m');
$day = date('d');
$html = '';
$html .= '<ul>';
if ($ranking_data) {
$i = 0;
foreach ($ranking_data as $entry) {
$i += 1;
$class = ($i % 2 == 0) ? 'even' : 'odd';
$html .= sprintf('<li class="no-%d %s"><a href="%s">%s</a></li>', $entry['no'], $class, $entry['url'], $entry['title']);
}
}
$html .= '</ul>';
$daily_ranking_html_path = RANKING_HTML_DIR . 'daily.html';
file_put_contents($daily_ranking_html_path, $html);
~$ cat lib/ranking.php
<?php
define('RANKING_DIR', '/home/www/example.com/ranking/');
define('RANKING_LOG_DIR', RANKING_DIR . 'logs/');
define('RANKING_HTML_DIR', RANKING_DIR . 'html/');
define('RANKING_MAX', 10);
function get_image_tag_for_access_log($id) {
return '<img src="' . AD_URL. 'access/access.php?id='.$id.'">';
}
function put_post_access_log($id) {
$log_string = $id . ',' . date('YmdHis') . "\n";
$log_file_path = RANKING_LOG_DIR . 'access_' . date('Ymd') . '.log';
$fp = fopen($log_file_path, 'a', LOCK_EX);
flock($fp, LOCK_EX);
fwrite($fp, $log_string);
fclose($fp);
}
function get_ranking_html() {
$html_path = RANKING_HTML_DIR . 'daily.html';
if (!file_exists($html_path)) {
return '';
}
$html = file_get_contents($html_path);
if ($html) {
return $html;
} else {
return '';
}
}
~$ cat htdocs/wp/wp-contents/themes/example/count_image.php
<?php
require_once '/home/www/example.com/lib/ranking.php';
if (array_key_exists('id', $_GET)) {
$id = intval($_GET['id']);
$key = 'access-' . $id;
session_start();
if (array_key_exists($key, $_SESSION) && $_SESSION[$key] > time() - 3600) {
// none;
} else {
$_SESSION[$key] = time();
put_post_access_log($id);
}
}
header("Content-type: image/gif");
echo base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');
~$ cat htdocs/wp/wp-contents/themes/example/functions.php
require_once '/home/www/example.com/lib/ranking.php';
bin/make_ranking_html.php
ログを集計してランキング用のHTMLを指定のディレクトリに出力します。
cron で一日一回実行することを想定してます。
lib/ranking.php
設定ファイルとか、ログの出力,HTMLの表示を行う関数を置いてます。
count_image.php
アクセスログを書き込むためのダミー画像です。base64_encodeされているのは1pxの透過gifです。
スクリプトは上記の状態で、
<img src="image.php?post-id=xx&r=<?php mt_rand(); ?>" />
といった画像を記事ページのテンプレートに埋め込み、ログを収集します。
最初はテンプレート内にPHPで関数読み込む様にしてましたが WP Super Cache でキャッシュ生成後はHTMLをそのまま返し PHP が動作しないのでしょうがなく画像埋め込み式にしました。Google Analytics のように JavaScript でも良いですね。
で、ログを出力した翌日の深夜にでも make_ranking_html.php を実行すると以下の様なHTMLが出力される。
<ul>
<li class="no-1 odd"><a href="/archives/8486/">入間人間のお父さんが小説を執筆?</a></li>
<li class="no-2 even"><a href="/archives/8445/">【このライトノベルが売れて欲しい!】第3回『テルミー』</a></li>
<li class="no-3 odd"><a href="/archives/8294/">【このライトノベルが売れて欲しい!】第2回『期間限定いもうと。』</a></li>
<li class="no-4 even"><a href="/archives/8192/">【記事紹介】「魔王な使い魔と魔法少女な」みみとミミさん、インタビュー</a></li>
<li class="no-5 odd"><a href="/archives/8479/">【ラノベの車窓から】第8回 『ヘルカム!』</a></li>
<li class="no-6 even"><a href="/archives/8431/">MF文庫J新刊特典情報</a></li>
<li class="no-7 odd"><a href="/archives/8467/">「はぐれ勇者の鬼畜美学」TVアニメ公式サイトがオープン</a></li>
<li class="no-8 even"><a href="/archives/8174/">講談社ラノベ文庫 4月新刊のジャケット・あらすじ紹介</a></li>
<li class="no-9 odd"><a href="/archives/4692/">「みんなで選ぶベストライトノベル2011」投票結果発表</a></li>
<li class="no-10 even"><a href="/archives/1580/">「ココロコネクト」アニメPV&アニメ化記念特集</a></li>
</ul>
どうみてもラノベニュースオンラインですが細かいことは気にしない。そのまま載せるのもアレなので一部改変してますが。
WordPress的には正しいやり方では無いと思うけど、HTML読み込むだけなのでプラグイン使うよりは速いしカスタマイズしやすいしプラグインだとcron使うのがアレだし(wp-cronはあるけど...)、これも良いかなーと思います。WPの中身は全くいじってないのでアップデートが来てもそのまま動きますしね。
というわけでPHPいじれるとカスタマイズの自由度が上がって良いですねという話でした。あれ?