WordPressでログイン成功・失敗・ログアウト・プラグインの操作に関する通知をChatwork・Slack・メールなどに送信している場合、その履歴をデータベースに保存し、管理画面から確認できる仕組みがあると運用上非常に便利です。
本記事では、通知ログを保存するだけでなく、WordPress管理画面に「通知ログ」メニューを追加し、そこから一覧表示する機能の実装方法を解説します。
機能の概要
- Chatwork/Slack/メールに送信された通知をログとして保存
- 通知ログをWordPressの管理画面で一覧表示
- ページネーションに対応
- メッセージ欄は折り返し表示で視認性良好
- データベースに保存されたログは、日時/ユーザー名/ロール/通知先/イベント種別などを表示
WP_List_Tableとは?
WP_List_Table
は WordPress 管理画面で一覧テーブル(投稿リストやユーザーリスト等)を表示する際に使われる内部クラスです。
今回のようにカスタムデータ(DBに保存された通知ログなど)を一覧表示する際も、このクラスを拡張することで次のような利点があります:
- ページネーション(ページ送り)機能
- ソートや検索の仕組み(後で追加可能)
- テーブルレイアウトの統一感
ファイル構成
この機能は以下の4つのファイルで構成されています:
multi-login-notifier-extended.php
: メインプラグインファイルsettings.php
: 通知先やユーザー制御に関する設定画面db.php
: 通知ログのDB保存およびテーブル作成処理admin-log-viewer.php
: 管理画面にログ表示用のメニューと画面を追加
各ファイルのソースコード全文
multi-login-notifier-extended.php
<?php
/**
* Plugin Name: Multi Login Notifier Extended
* Description: WordPressのログイン成功・失敗・ログアウト・プラグインの有効化/無効化をChatwork/Slack/メールで通知。通知種別の制御も可能。
* Version: 1.5.3
* Author: Your Name
*/
// WordPress外からのアクセスを防止
defined('ABSPATH') || exit;
// 設定画面とオプション管理の読み込み
require_once plugin_dir_path(__FILE__) . 'includes/settings.php';
//
require_once plugin_dir_path(__FILE__) . 'includes/admin-log-viewer.php';
// プラグイン有効化時に通知ログ用テーブルを作成
register_activation_hook(__FILE__, 'mln_create_notification_log_table');
/**
* ログイン成功時の通知
*/
add_action('wp_login', function ($user_login, $user) {
if (mln_should_skip_notification($user_login, $user->roles)) return;
$msg = mln_build_message('ログイン成功通知', $user_login, $user->roles);
mln_send_notifications([
'type' => 'login',
'title' => "ログイン成功: {$user_login}",
'body' => $msg,
'user' => $user
]);
}, 10, 2);
/**
* ログアウト時の通知(Cookieからユーザー名を取得)
*/
add_action('wp_logout', function () {
if (isset($_COOKIE[LOGGED_IN_COOKIE])) {
$cookie_parts = explode('|', $_COOKIE[LOGGED_IN_COOKIE]);
$username = $cookie_parts[0] ?? null;
if ($username) {
$user = get_user_by('login', $username);
if ($user && !mln_should_skip_notification($user->user_login, $user->roles)) {
$msg = mln_build_message('ログアウト通知', $user->user_login, $user->roles);
mln_send_notifications([
'type' => 'logout',
'title' => "ログアウト: {$user->user_login}",
'body' => $msg,
'user' => $user
]);
}
}
}
});
/**
* ログイン失敗時の通知(ユーザーが存在しないため user は null)
*/
add_action('wp_login_failed', function ($username) {
$site = get_bloginfo('name');
$url = home_url();
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$time = current_time('mysql');
$msg = "[ログイン失敗通知]\n"
. "ユーザー名: {$username}\n"
. "日時: {$time}\n"
. "サイト: {$site}\n"
. "URL: {$url}\n"
. "IPアドレス: {$ip}";
mln_send_notifications([
'type' => 'fail',
'title' => "ログイン失敗: {$username}",
'body' => $msg,
'user' => null
]);
});
/**
* プラグイン自身の有効化通知
*/
register_activation_hook(__FILE__, function () {
if (!function_exists('get_plugin_data')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$data = get_plugin_data(__FILE__);
mln_notify_plugin_status_change($data['Name'], $data['Version'], '有効化');
});
/**
* 他プラグインの有効化/無効化時の通知
*/
add_action('activated_plugin', function ($plugin_path) {
if ($plugin_path === plugin_basename(__FILE__)) return; // 自プラグインは除外
mln_handle_plugin_event($plugin_path, '有効化');
});
add_action('deactivated_plugin', function ($plugin_path) {
if ($plugin_path === plugin_basename(__FILE__)) return; // 自プラグインは除外
mln_handle_plugin_event($plugin_path, '無効化');
});
/**
* 通知対象外のユーザー・ロールを判定
*/
function mln_should_skip_notification($username, $roles) {
$excluded_users = array_map('trim', explode(',', get_option('mln_excluded_users', '')));
if (in_array($username, $excluded_users, true)) return true;
$allowed_roles = array_map('trim', explode(',', get_option('mln_allowed_roles', '')));
if (!empty($allowed_roles) && !array_intersect($roles, $allowed_roles)) return true;
return false;
}
/**
* 通知メッセージの共通フォーマットを生成
*/
function mln_build_message($type, $username, $roles) {
$site = get_bloginfo('name');
$url = home_url();
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$time = current_time('mysql');
$roles_text = implode(', ', $roles);
return "[{$type}]\n"
. "ユーザー: {$username}\n"
. "権限: {$roles_text}\n"
. "日時: {$time}\n"
. "サイト: {$site}\n"
. "URL: {$url}\n"
. "IPアドレス: {$ip}";
}
/**
* プラグインの有効化/無効化イベントを処理
*/
function mln_handle_plugin_event($plugin_path, $status = '有効化') {
if (!function_exists('get_plugin_data')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$plugin_file = WP_PLUGIN_DIR . '/' . $plugin_path;
if (!file_exists($plugin_file)) return;
$plugin_data = get_plugin_data($plugin_file);
mln_notify_plugin_status_change($plugin_data['Name'], $plugin_data['Version'], $status);
}
/**
* プラグインの状態変化通知(Chatwork, Slack, メール, DB)
*/
function mln_notify_plugin_status_change($name, $version, $status = '有効化') {
$site = get_bloginfo('name');
$url = home_url();
$time = current_time('mysql');
$msg = "[プラグイン{$status}通知]\n"
. "サイト: {$site}\n"
. "プラグイン: {$name} ({$version})\n"
. "URL: {$url}\n"
. "時間: {$time}";
mln_send_notifications([
'type' => 'plugin',
'title' => "プラグイン{$status}: {$name}",
'body' => $msg,
'user' => wp_get_current_user()
]);
}
/**
* 通知の送信本体(通知先制御 & DB保存)
*/
function mln_send_notifications($params) {
$type = $params['type'];
$body = $params['body'];
$title = $params['title'];
$user = $params['user'];
$sent_to = [];
// Chatwork通知
if (
get_option('mln_chatwork_enabled') === '1' &&
get_option("mln_chatwork_notify_{$type}") === '1'
) {
$token = get_option('mln_chatwork_token');
$room = get_option('mln_chatwork_room_id');
if ($token && $room) {
wp_remote_post("https://api.chatwork.com/v2/rooms/{$room}/messages", [
'headers' => ['X-ChatWorkToken' => $token],
'body' => ['body' => $body],
]);
$sent_to[] = 'chatwork';
}
}
// Slack通知
if (
get_option('mln_slack_enabled') === '1' &&
get_option("mln_slack_notify_{$type}") === '1'
) {
$webhook = get_option('mln_slack_webhook_url');
if ($webhook) {
wp_remote_post($webhook, [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['text' => $body]),
]);
$sent_to[] = 'slack';
}
}
// メール通知
if (
get_option('mln_email_enabled') === '1' &&
get_option("mln_email_notify_{$type}") === '1'
) {
$to = get_option('mln_email_to');
if ($to) {
wp_mail($to, $title, $body);
$sent_to[] = 'email';
}
}
// DBログ記録(ユーザーが存在する場合のみ)
if ($user && isset($user->user_login, $user->roles)) {
$username = $user->user_login;
$roles = $user->roles;
$notified = implode(',', $sent_to);
if (!function_exists('mln_log_notification_to_db')) {
require_once plugin_dir_path(__FILE__) . 'includes/db.php';
}
mln_log_notification_to_db($type, $username, implode(',', $roles), $body, $notified);
}
}
includes/settings.php
<?php
defined('ABSPATH') || exit;
add_action('admin_menu', function () {
add_options_page(
'Login Notifier Settings',
'Login Notifier',
'manage_options',
'mln-settings',
'mln_render_settings_page'
);
});
function mln_render_settings_page() {
?>
<div class="wrap">
<h1>Multi Login Notifier Extended 設定</h1>
<form method="post" action="options.php">
<?php
settings_fields('mln_settings');
do_settings_sections('mln-settings');
submit_button();
?>
</form>
</div>
<?php
}
add_action('admin_init', function () {
// 共通設定
register_setting('mln_settings', 'mln_allowed_roles');
register_setting('mln_settings', 'mln_excluded_users');
// Chatwork
register_setting('mln_settings', 'mln_chatwork_enabled');
register_setting('mln_settings', 'mln_chatwork_token');
register_setting('mln_settings', 'mln_chatwork_room_id');
register_setting('mln_settings', 'mln_chatwork_notify_login');
register_setting('mln_settings', 'mln_chatwork_notify_logout');
register_setting('mln_settings', 'mln_chatwork_notify_fail');
register_setting('mln_settings', 'mln_chatwork_notify_plugin');
// Slack
register_setting('mln_settings', 'mln_slack_enabled');
register_setting('mln_settings', 'mln_slack_webhook_url');
register_setting('mln_settings', 'mln_slack_notify_login');
register_setting('mln_settings', 'mln_slack_notify_logout');
register_setting('mln_settings', 'mln_slack_notify_fail');
register_setting('mln_settings', 'mln_slack_notify_plugin');
// Email
register_setting('mln_settings', 'mln_email_enabled');
register_setting('mln_settings', 'mln_email_to');
register_setting('mln_settings', 'mln_email_notify_login');
register_setting('mln_settings', 'mln_email_notify_logout');
register_setting('mln_settings', 'mln_email_notify_fail');
register_setting('mln_settings', 'mln_email_notify_plugin');
// セクション
add_settings_section('mln_main', '通知設定', null, 'mln-settings');
// Chatwork 設定
mln_add_checkbox('mln_chatwork_enabled', 'Chatwork通知を有効化');
mln_add_text('mln_chatwork_token', 'Chatwork APIトークン');
mln_add_text('mln_chatwork_room_id', 'ChatworkルームID');
mln_add_checkbox('mln_chatwork_notify_login', 'ログイン成功通知');
mln_add_checkbox('mln_chatwork_notify_logout', 'ログアウト通知');
mln_add_checkbox('mln_chatwork_notify_fail', 'ログイン失敗通知');
mln_add_checkbox('mln_chatwork_notify_plugin', 'プラグイン操作通知');
// Slack 設定
mln_add_checkbox('mln_slack_enabled', 'Slack通知を有効化');
mln_add_text('mln_slack_webhook_url', 'Slack Webhook URL');
mln_add_checkbox('mln_slack_notify_login', 'ログイン成功通知');
mln_add_checkbox('mln_slack_notify_logout', 'ログアウト通知');
mln_add_checkbox('mln_slack_notify_fail', 'ログイン失敗通知');
mln_add_checkbox('mln_slack_notify_plugin', 'プラグイン操作通知');
// メール通知設定
mln_add_checkbox('mln_email_enabled', 'メール通知を有効化');
mln_add_text('mln_email_to', '通知先メールアドレス');
mln_add_checkbox('mln_email_notify_login', 'ログイン成功通知');
mln_add_checkbox('mln_email_notify_logout', 'ログアウト通知');
mln_add_checkbox('mln_email_notify_fail', 'ログイン失敗通知');
mln_add_checkbox('mln_email_notify_plugin', 'プラグイン操作通知');
// ユーザー制御設定
mln_add_text('mln_allowed_roles', '通知対象のロール(カンマ区切り)');
mln_add_text('mln_excluded_users', '通知除外ユーザー名(カンマ区切り)');
});
/**
* 入力欄ヘルパー(テキスト)
*/
function mln_add_text($option_name, $label) {
add_settings_field($option_name, $label, function () use ($option_name) {
$value = esc_attr(get_option($option_name));
echo "<input type='text' name='{$option_name}' value='{$value}' class='regular-text' />";
}, 'mln-settings', 'mln_main');
}
/**
* 入力欄ヘルパー(チェックボックス)
*/
function mln_add_checkbox($option_name, $label) {
add_settings_field($option_name, $label, function () use ($option_name) {
$value = get_option($option_name);
echo "<input type='checkbox' name='{$option_name}' value='1' " . checked(1, $value, false) . " />";
}, 'mln-settings', 'mln_main');
}
includes/db.php
<?php
/**
* DB操作用ファイル
* 通知ログテーブルの作成とログ保存処理を提供
*/
if (!defined('ABSPATH')) exit;
/**
* 通知ログテーブルの作成
* プラグイン有効化時、または初回通知時に実行
*/
function mln_create_notification_log_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'mln_notification_logs';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_type VARCHAR(50) NOT NULL,
user_login VARCHAR(60) DEFAULT '',
role VARCHAR(100) DEFAULT '',
message TEXT NOT NULL,
notified_to TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
/**
* 通知ログをDBに保存
* @param string $event_type イベント種別(login, logout, fail, plugin_activate など)
* @param string $user_login ユーザー名(取得できない場合は空)
* @param string $role ロール名(カンマ区切り)
* @param string $message 通知内容
* @param string|array $notified_to 通知先(chatwork, slack, email など)
*/
function mln_log_notification_to_db($event_type, $user_login, $role, $message, $notified_to) {
global $wpdb;
$table_name = $wpdb->prefix . 'mln_notification_logs';
// テーブルが存在しない場合は作成
if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== $table_name) {
mln_create_notification_log_table();
}
// 通知先が配列の場合はカンマで結合、文字列の場合はそのまま使用、その他は空文字
if (is_array($notified_to)) {
$notified_str = implode(',', $notified_to);
} elseif (is_string($notified_to)) {
$notified_str = $notified_to;
} else {
$notified_str = '';
}
// レコードを挿入
$wpdb->insert(
$table_name,
[
'event_type' => $event_type,
'user_login' => $user_login,
'role' => $role,
'message' => $message,
'notified_to' => $notified_str,
'created_at' => current_time('mysql', 1)
],
[
'%s', '%s', '%s', '%s', '%s', '%s'
]
);
}
includes/admin-log-viewer.php
<?php
/**
* 通知ログ表示用管理画面(WP_List_Table 使用)
*/
if (!class_exists('WP_List_Table')) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class MLN_Notification_Log_Table extends WP_List_Table {
public function get_columns() {
return [
'created_at' => '日時',
'event_type' => '種別',
'user_login' => 'ユーザー',
'role' => 'ロール',
'notified_to' => '通知先',
'message' => 'メッセージ',
];
}
public function prepare_items() {
global $wpdb;
$table_name = $wpdb->prefix . 'mln_notification_logs';
$columns = $this->get_columns();
$this->_column_headers = [$columns, [], []];
$per_page = 20;
$current_page = $this->get_pagenum();
$offset = ($current_page - 1) * $per_page;
$total_items = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}");
$this->items = $wpdb->get_results(
$wpdb->prepare("SELECT * FROM {$table_name} ORDER BY created_at DESC LIMIT %d OFFSET %d", $per_page, $offset),
ARRAY_A
);
$this->set_pagination_args([
'total_items' => $total_items,
'per_page' => $per_page,
]);
}
public function column_default($item, $column_name) {
if (isset($item[$column_name])) {
if ($column_name === 'message') {
return '<pre style="white-space:pre-wrap;max-width:800px;">' . esc_html($item[$column_name]) . '</pre>';
}
return esc_html($item[$column_name]);
}
return '';
}
}
add_action('admin_menu', function () {
add_menu_page(
'通知ログ',
'通知ログ',
'manage_options',
'mln-log-viewer',
function () {
echo '<div class="wrap"><h1>通知ログ</h1>';
$table = new MLN_Notification_Log_Table();
$table->prepare_items();
echo '<form method="get">';
$table->display();
echo '</form></div>';
},
'dashicons-list-view',
30
);
});
テーブル構造(mln_notification_logs)
カラム名 | 型 | 説明 |
---|---|---|
id | BIGINT | 主キー(AUTO_INCREMENT) |
event_type | VARCHAR(50) | login / logout / plugin 等 |
user_login | VARCHAR(60) | ユーザー名 |
role | VARCHAR(100) | 権限(administrator など) |
message | TEXT | 通知内容 |
notified_to | TEXT | 通知先(chatwork, slack等) |
created_at | DATETIME | 通知日時(CURRENT_TIMESTAMP) |
まとめと今後の展望
このように、通知内容をログとしてDBに保存し、管理画面で可視化することにより、トラブル対応やセキュリティチェックの効率を高めることができます。
今後以下のような機能拡張も可能です:
- ログの絞り込み(ユーザー別、日付範囲など)
- キーワード検索機能
- CSVダウンロード機能
- ログの削除機能(定期的/手動)
運用に合わせて柔軟に拡張していける仕組みとなっています。
コメント