<?php if ( !defined( 'ABSPATH' ) ) exit;//PHPの宣言と直接アクセスの拒否

/*** nonceフィールドの指定 ***/
function haup_limit_login_form_nonce() {
	wp_nonce_field('haup_limit_login_nonce_action', 'haup_limit_login_nonce');
}
add_action('login_form', 'haup_limit_login_form_nonce');


/***** ログイン試行回数制限 *****/
/* ログイン失敗時に一定時間メッセージを出す */
function haup_limit_login_attempts() {
	global $wpdb;
	$table_name = $wpdb->prefix . 'haup_failed_logins';

	// Nonceの検証
	if (isset($_POST['haup_limit_login_nonce']) && !wp_verify_nonce(sanitize_text_field( wp_unslash($_POST['haup_limit_login_nonce'])), 'haup_limit_login_nonce_action')) {
		wp_die(esc_html__('Nonce verification failed.', 'hima-art-utility'));
	}

	// 最大試行回数
	$max_attempts = 3; //初期値は3回失敗まで
	$max_attempts = apply_filters('haup_change_max_attemps',$max_attempts); //フィルター

	// ロックアウト時間（ここでは5分）
	$lock_sec = 5;//ログイン試行に失敗したら5分ロックアウト
	$lock_sec = apply_filters('haup_change_lockout_time',$lock_sec); //フィルター
	$lockout_time_seconds = $lock_sec * MINUTE_IN_SECONDS; // 秒単位で計算

	// 現在のユーザーIPアドレスとユーザー名を取得
	$ip_address = '';
	$username = '';

	if(isset($_SERVER['REMOTE_ADDR'])){
		$ip_address = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
	}

	if (isset($_POST['log'])) {
		$username = sanitize_user(wp_unslash($_POST['log']));
	}

	// データベースから現在の試行情報を取得
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery , PluginCheck.Security.DirectDB.UnescapedDBParameter
	$current_record = $wpdb->get_row(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			"SELECT * FROM $table_name WHERE ip_address = %s AND username = %s",
			$ip_address,
			$username
		)
	); 

	$failed_login_count = $current_record ? $current_record->attempts : 0;

// strtotime() の Deprecated 警告対策
$lockout_until_timestamp = 0; // デフォルト値はタイムスタンプで0
if ( $current_record && !empty($current_record->lockout_until) ) {
    $lockout_until_timestamp = strtotime($current_record->lockout_until);
}

	//ロックアウト中のメッセージ
	$message = __('Login failed. Are you a spammer?','hima-art-utility');
	$message = apply_filters('haup_change_locked_out_message',$message);

	// ロックアウト中か確認
	if ($failed_login_count >= $max_attempts) {
		if (time() < $lockout_until_timestamp) { // 現在時刻がロックアウト解除時刻より前ならロックアウト中
			wp_die( esc_html($message) );
		} else {
			// ロックアウト時間が経過した場合、カウンターをリセット
			// ここではレコードを削除せず、attemptsをリセット＆lockout_untilをNULLにする
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->update(
				$table_name,
				array(
					'attempts' => 0,
					'first_attempt' => null, // 最初の試行時刻もリセット
					'last_attempt' => current_time('mysql', true), // 更新時間だけ最新にする
					'lockout_until' => null
				),
				array(
					'ip_address' => $ip_address,
					'username' => $username
				),
				array('%d', '%s', '%s', '%s'),
				array('%s', '%s')
			);
			$failed_login_count = 0; // カウントをリセットして次の処理へ
		}
	}

}
add_action('init', 'haup_limit_login_attempts'); 

/* --- ログイン失敗時に正確にカウントアップするフック --- */
// wp_login_failed アクションは、WordPressがログイン失敗を確定した後に発火します。
function haup_record_failed_login($username) {
	global $wpdb;
	$table_name = $wpdb->prefix . 'haup_failed_logins';

	$ip_address = ''; // デフォルト値を設定
	if (isset($_SERVER['REMOTE_ADDR'])) {
		$ip_address = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
	}

	//$ip_address = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
	$username = sanitize_user($username); // wp_login_failed から渡されるユーザー名は既にサニタイズされている可能性が高いが念のため

	$max_attempts = apply_filters('haup_change_max_attemps', 3);
	$lock_sec = apply_filters('haup_change_lockout_time', 5);
	$lockout_time_seconds = $lock_sec * MINUTE_IN_SECONDS;

	$now_mysql = current_time('mysql', true);
	$lockout_datetime = gmdate('Y-m-d H:i:s', time() + $lockout_time_seconds);

	// 既存のレコードを取得または新規作成
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery , PluginCheck.Security.DirectDB.UnescapedDBParameter
	$current_record = $wpdb->get_row(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			"SELECT * FROM $table_name WHERE ip_address = %s AND username = %s",
			$ip_address,
			$username
		)
	);

	if ($current_record) {
		// 既存レコードがあれば更新
		$new_attempts = $current_record->attempts + 1;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->update(
			$table_name,
			array(
				'attempts' => $new_attempts,
				'last_attempt' => $now_mysql,
				'lockout_until' => ($new_attempts >= $max_attempts) ? $lockout_datetime : null
			),
			array(
				'ip_address' => $ip_address,
				'username' => $username
			),
			array('%d', '%s', '%s'),
			array('%s', '%s')
		);
	} else {
		// 新規レコードを挿入
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->insert(
			$table_name,
			array(
				'ip_address' => $ip_address,
				'username' => $username,
				'attempts' => 1,
				'first_attempt' => $now_mysql,
				'last_attempt' => $now_mysql,
				'lockout_until' => null // 最初の試行ではロックアウトは設定しない
			),
			array('%s', '%s', '%d', '%s', '%s', '%s')
		);
	}
}
add_action('wp_login_failed', 'haup_record_failed_login');


/* ログイン成功時に試行カウントをリセット */
function haup_reset_failed_login_count_on_success($username, $user) {
	global $wpdb;
	$table_name = $wpdb->prefix . 'haup_failed_logins';

	$ip_address = '';//変数の初期化
	if(isset( $_SERVER['REMOTE_ADDR'] )){
		$ip_address = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
	}

	// 該当レコードの試行回数をリセットし、ロックアウトも解除
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery
	$wpdb->update(
		$table_name,
		array(
			'attempts' => 0,
			'first_attempt' => null,
			'last_attempt' => current_time('mysql', true),
			'lockout_until' => null
		),
		array(
			'ip_address' => $ip_address
		),
		array('%d', '%s', '%s', '%s'), // データ型
		array('%s', '%s') // WHERE句のデータ型
	);

}
add_action('wp_login', 'haup_reset_failed_login_count_on_success', 10, 2);


/* (オプション) 古いレコードを定期的にクリーンアップする関数 */
function haup_cleanup_old_failed_logins() {
	global $wpdb;
	$table_name = $wpdb->prefix . 'haup_failed_logins';

	$retention_period_parts = apply_filters('haup_cleanup_retention_period', ['1', 'MONTH']);
	$value = absint($retention_period_parts[0]);
	$unit = sanitize_key($retention_period_parts[1]);

	$valid_units = ['SECOND', 'MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'];
	if (! in_array(strtoupper($unit), $valid_units, true)) {
		$unit = 'MONTH';
	}

// phpcs:ignore WordPress.DB.DirectDatabaseQuery , PluginCheck.Security.DirectDB.UnescapedDBParameter	
	$wpdb->query(
		$wpdb->prepare(
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared , WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			"DELETE FROM `{$table_name}` WHERE `last_attempt` < DATE_SUB(NOW(), INTERVAL %d " . $unit . ")",
			$value
		)
	);
}

/* 週に一度不要なデータの削除を実行するためのフック (WP Cronを使用) */
/* 削除対象となるデータの期間についてはhaup_cleanup_old_failed_logins内で指定 */
if ( ! wp_next_scheduled( 'haup_cleanup_old_failed_logins_event' ) ) {
	wp_schedule_event( time(), 'weekly', 'haup_cleanup_old_failed_logins_event' );
}
add_action( 'haup_cleanup_old_failed_logins_event', 'haup_cleanup_old_failed_logins' );


/***** フィルター例 *****/
/* 回数を変更 */
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
function change_max_attempts($max_attempts){
	$max_attempts = 3;//変更する回数を指定
	return $max_attempts;
}
//add_filter('haup_change_max_attemps','change_max_attempts');

/* ロックアウト時間を変更 */
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
function change_lock_time($lock_sec){
	$lock_sec = 5;//変更する分数を指定
	return $lock_sec;
}
//add_filter('haup_change_lockout_time','change_lock_time');

/* メッセージを変更 */
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
function change_lockout_message($message){
	$message = 'スパム行為はやめてください';//変更するメッセージを指定
	return $message;
}
//add_filter('haup_change_locked_out_message','change_lockout_message');

/* 削除対象とするデータの期間を変更 */
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
function change_retention($period) {

	//「期間の指定」に設定する値　デフォルトは1か月（1 MONTH）
	//指定例（数字と期間の間は半角1文字開けること）
	// 1日＝1 DAY
	// 1週間＝1 WEEK

	return '期間の指定'; // ここに指定しなおしたい期間を設定
}
//add_filter('haup_cleanup_retention_period', 'change_retention');