この記事では、WordPressのクラシックエディタで使いやすいショートコード型の簡易アクセスカウンターを作成する方法をまとめます。カウンターは、同じIPアドレスからのアクセスを重複して記録せず、合計・昨日・今日のユニークアクセス数を表示します 。
実際のサーバ名、ユーザー名、WordPressの実パスなどは環境ごとに異なるため、ここではダミー値で説明します。
パターンA: WordPressクラシックエディタ用
クラシックエディタでは、記事本文や固定ページ本文にショートコードを貼るだけで表示できる形式が扱いやすいです。今回の例では、次のショートコードでカウンターを表示します。
[unique_counter]
表示例は次のようになります。
合計: 123 昨日: 12 今日: 8
今回の仕様
表示する項目
- 合計: 日別ユニークアクセス数の累計
- 昨日: 昨日アクセスしたユニークIP数
- 今日: 今日アクセスしたユニークIP数
重複カウントの扱い
同じIPアドレスから同じ日に複数回アクセスがあっても、その日のカウントは1回だけです。ただし、同じIPアドレスでも別の日にアクセスした場合は、合計に追加されます。
たとえば、同じIPから今日3回アクセスがあっても、今日のカウントは1だけ増えます。翌日に同じIPからアクセスがあった場合、翌日のカウントにも1として記録され、合計も1増えます。
IPアドレスの保存方法
IPアドレスそのものは保存せず、WordPressのsalt値を使ってハッシュ化した値を保存します。これにより、DB上に生のIPアドレスを残さない構成にできます。
hash_hmac('sha256', $ip_address, wp_salt('auth'))
ファイル構成
WordPress本体やテーマは変更せず、通常のプラグインとして追加します。
/path/to/wordpress/wp-content/plugins/simple-unique-counter/ simple-unique-counter.php uninstall.php
通常プラグインにしておくと、WordPress管理画面から無効化・削除ができるため、問題があった場合に戻しやすくなります。
DB仕様
今回のカウンターでは、WordPressのDB内に専用テーブルを2つ作成します。テーブル接頭辞は環境によって変わるため、ここでは {prefix} と表記します。
全期間IP管理用テーブル
{prefix}_simple_unique_counter_ips
このテーブルは、IPハッシュの初回アクセス日と最終アクセス日を持つ管理用です。IPハッシュを主キーにして、同じIPハッシュが複数回登録されないようにします。現在の合計表示はこのテーブルではなく、日別テーブルの行数から算出します。
ip_hash varchar(64) PRIMARY KEY first_seen datetime last_seen datetime
日別ユニークIP用テーブル
{prefix}_simple_unique_counter_daily
このテーブルは、今日・昨日のカウントを出すために使います。日付とIPハッシュの組み合わせを主キーにして、同じ日に同じIPが複数回記録されないようにします。
hit_date date ip_hash varchar(64) first_seen datetime last_seen datetime PRIMARY KEY (hit_date, ip_hash)
カウント処理の流れ
1. 表示位置にショートコードを置く
クラシックエディタの記事本文や固定ページに、次のショートコードを貼ります。
[unique_counter]
2. 表示枠を出す
ショートコードは、まず次のような表示枠をHTMLとして出力します。
<span class="simple-unique-counter"> 合計: <span data-count="total">-</span> 昨日: <span data-count="yesterday">-</span> 今日: <span data-count="today">-</span> </span>
3. ブラウザからREST APIへアクセスする
ページキャッシュがある環境では、PHPだけで数字を直接出すと表示が古くなることがあります。そのため、表示後にブラウザ側のJavaScriptからREST APIへアクセスして、最新の数字を取得します。
POST https://example.com/wp-json/simple-unique-counter/v1/hit
4. IPをハッシュ化して記録する
REST APIが呼ばれると、アクセス元IPを取得し、ハッシュ化してDBに保存します。
INSERT INTO {prefix}_simple_unique_counter_ips
(ip_hash, first_seen, last_seen)
VALUES
(:ip_hash, :now, :now)
ON DUPLICATE KEY UPDATE
last_seen = VALUES(last_seen)
日別テーブルにも同じように保存します。
INSERT INTO {prefix}_simple_unique_counter_daily
(hit_date, ip_hash, first_seen, last_seen)
VALUES
(:today, :ip_hash, :now, :now)
ON DUPLICATE KEY UPDATE
last_seen = VALUES(last_seen)
5. 件数を返す
最後に、合計・昨日・今日の件数をJSONで返します。
{
"total": 123,
"yesterday": 12,
"today": 8
}
プラグインPHPの構成
プラグイン本体は simple-unique-counter.php にまとめます。WordPressの通常プラグインとして認識されるように、ファイル先頭にはプラグインヘッダーを書きます。
<?php
/**
* Plugin Name: Simple Unique Counter
* Description: Displays total, yesterday, and today unique visitor counts by hashed IP address.
* Version: 1.0.1
* Author: OpenClaw
* License: GPL-2.0-or-later
*/
if (!defined('ABSPATH')) {
exit;
}
ABSPATH が定義されていない場合は処理を止めています。これは、プラグインPHPへ直接アクセスされたときに実行されないようにするための定番の防御です。
初期化処理
プラグインでは、クラスの init() で必要なWordPressフックを登録します。
public static function init(): void
{
register_activation_hook(__FILE__, [self::class, 'activate']);
add_action('rest_api_init', [self::class, 'register_rest_routes']);
add_shortcode('unique_counter', [self::class, 'shortcode']);
add_action('wp_ajax_simple_unique_counter_hit', [self::class, 'ajax_hit']);
add_action('wp_ajax_nopriv_simple_unique_counter_hit', [self::class, 'ajax_hit']);
}
register_activation_hook() は、プラグイン有効化時にDBテーブルを作るために使います。add_shortcode() は [unique_counter] を使えるようにするための登録です。
今回の表示はREST API方式ですが、admin-ajax.php でも呼べるように wp_ajax_* も残しています。環境によってRESTを使いにくい場合の保険になります。
有効化時にDBテーブルを作る
プラグインを有効化すると、activate() が実行されます。ここでWordPressの dbDelta() を使って、必要なテーブルを作成します。
public static function activate(): void
{
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$charset_collate = $wpdb->get_charset_collate();
$ip_table = self::ip_table();
$daily_table = self::daily_table();
dbDelta("
CREATE TABLE {$ip_table} (
ip_hash varchar(64) NOT NULL,
first_seen datetime NOT NULL,
last_seen datetime NOT NULL,
PRIMARY KEY (ip_hash)
) {$charset_collate};
");
dbDelta("
CREATE TABLE {$daily_table} (
hit_date date NOT NULL,
ip_hash varchar(64) NOT NULL,
first_seen datetime NOT NULL,
last_seen datetime NOT NULL,
PRIMARY KEY (hit_date, ip_hash),
KEY hit_date (hit_date)
) {$charset_collate};
");
}
$wpdb->prefix を使ってテーブル名を作るため、WordPressのテーブル接頭辞が wp_ 以外でも動作します。
ショートコードの出力
shortcode() は、記事本文に表示するHTMLとJavaScriptを返します。最初は数字を - で表示し、あとからREST APIの結果で置き換えます。
<span class="simple-unique-counter" data-rest-url="https://example.com/wp-json/simple-unique-counter/v1/hit"> <span class="simple-unique-counter__item simple-unique-counter__total">合計: <span data-count="total">-</span></span> <span class="simple-unique-counter__item simple-unique-counter__yesterday">昨日: <span data-count="yesterday">-</span></span> <span class="simple-unique-counter__item simple-unique-counter__today">今日: <span data-count="today">-</span></span> </span>
JavaScript側では、data-rest-url に入っているREST APIへ POST して、返ってきたJSONで数字を書き換えます。
fetch(root.getAttribute('data-rest-url'), {
method: 'POST',
credentials: 'same-origin',
cache: 'no-store'
}).then(function (response) {
return response.json();
}).then(function (payload) {
['total', 'yesterday', 'today'].forEach(function (key) {
var target = root.querySelector('[data-count="' + key + '"]');
if (target && payload[key] !== undefined) {
target.textContent = payload[key];
}
});
});
REST APIの登録
REST APIは register_rest_route() で登録します。今回は公開ページの表示用なので、ログインしていない閲覧者からも呼べるように permission_callback は __return_true にしています。
public static function register_rest_routes(): void
{
register_rest_route('simple-unique-counter/v1', '/hit', [
'methods' => 'POST',
'callback' => [self::class, 'rest_hit'],
'permission_callback' => '__return_true',
]);
}
REST APIが呼ばれると rest_hit() が動き、アクセスを記録してから現在の件数を返します。
public static function rest_hit(): WP_REST_Response
{
self::record_hit();
return rest_ensure_response(self::counts());
}
アクセス記録処理
record_hit() は、IPハッシュを作り、全期間テーブルと日別テーブルへ保存します。すでに同じキーがある場合は、件数を増やさず last_seen だけ更新します。
$wpdb->query($wpdb->prepare(
'INSERT INTO ' . self::ip_table() . ' (ip_hash, first_seen, last_seen) VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE last_seen = VALUES(last_seen)',
$ip_hash,
$now,
$now
));
日別テーブルでは、hit_date と ip_hash の組み合わせが重複しないようにしています。
$wpdb->query($wpdb->prepare(
'INSERT INTO ' . self::daily_table() . ' (hit_date, ip_hash, first_seen, last_seen) VALUES (%s, %s, %s, %s)
ON DUPLICATE KEY UPDATE last_seen = VALUES(last_seen)',
$today,
$ip_hash,
$now,
$now
));
件数の取得
counts() では、日別テーブルの全行数を合計として取得し、同じ日別テーブルから昨日と今日の行数を取得します。これにより、同じIPでも日付が違えば合計に加算されます。
return [
'total' => (int) $wpdb->get_var('SELECT COUNT(*) FROM ' . self::daily_table()),
'yesterday' => (int) $wpdb->get_var($wpdb->prepare(
'SELECT COUNT(*) FROM ' . self::daily_table() . ' WHERE hit_date = %s',
$yesterday
)),
'today' => (int) $wpdb->get_var($wpdb->prepare(
'SELECT COUNT(*) FROM ' . self::daily_table() . ' WHERE hit_date = %s',
$today
)),
];
日付は current_time() を使って取得します。これにより、WordPress管理画面で設定しているタイムゾーンに合わせやすくなります。
IPアドレスの取得とハッシュ化
アクセス元IPは、Cloudflare配下なら HTTP_CF_CONNECTING_IP を優先し、それがなければ REMOTE_ADDR を使います。取得した値は filter_var() でIPアドレスとして妥当か確認します。
private static function client_ip(): string
{
$headers = [
'HTTP_CF_CONNECTING_IP',
'REMOTE_ADDR',
];
foreach ($headers as $header) {
if (empty($_SERVER[$header])) {
continue;
}
$ip = trim((string) wp_unslash($_SERVER[$header]));
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
return '';
}
取得したIPアドレスは、そのままDBに保存せず、WordPressのsalt値でHMACハッシュにします。
private static function ip_hash(): string
{
$ip = self::client_ip();
if ($ip === '') {
return '';
}
return hash_hmac('sha256', $ip, wp_salt('auth'));
}
アンインストール処理
uninstall.php では、プラグイン削除時に専用テーブルとオプションを削除します。無効化だけでは削除されず、WordPress管理画面でプラグインを削除したときだけ実行されます。
<?php
if (!defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
global $wpdb;
$wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'simple_unique_counter_daily');
$wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'simple_unique_counter_ips');
delete_option('simple_unique_counter_db_version');
REST API方式にした理由
WordPressでは、キャッシュ系プラグインが入っていると、記事本文内のショートコード結果がキャッシュされることがあります。アクセスカウンターのように数字が変わる部品では、ページ本体をキャッシュしつつ、数字だけを別リクエストで取得する方式が 扱いやすいです。
この構成なら、スマホでもPCでもJavaScriptが有効な通常ブラウザで表示できます。
戻し方
一時停止したい場合
WordPress管理画面で、プラグイン「Simple Unique Counter」を無効化します。無効化だけならDBテーブルは残るため、再度有効化すればカウントを継続できます。
完全に削除したい場合
プラグインを削除すると、uninstall.php によって専用テーブルも削除されます。
DROP TABLE IF EXISTS {prefix}_simple_unique_counter_daily;
DROP TABLE IF EXISTS {prefix}_simple_unique_counter_ips;
注意点
- 同じ会社、同じ家庭、同じ携帯キャリア回線では、複数人が同じIPとして見える場合があります。
- IPv6環境では、利用者側の環境によってIPが変わりやすい場合があります。
- Cloudflareなどのリバースプロキシ配下では、実IPを取得するヘッダーの扱いを環境に合わせる必要があります。
- 厳密なアクセス解析が必要な場合は、MatomoやGoogle Analyticsなどの解析ツールを使う方が向いています。
まとめ
今回の方式では、WordPressに小さな通常プラグインを追加し、ショートコードで「合計・昨日・今日」の簡易ユニークカウンターを表示します。IPアドレスはハッシュ化して保存し、DBにはIP管理用と日別集計用の2テーブルを作ります。合計は日別ユニークアクセスの累計として扱うため、同じIPでも日付が違えば加算されます。
クラシックエディタでは [unique_counter] を貼るだけで使えるため、既存テーマを変更せずに導入できます。問題があればプラグインを無効化、不要になったら削除で戻せる構成です。
設定~手順作成までにかかったAI費用(概算)
ChatGPT Plus:\0(¥3000円/月サブスクの範囲内)

