PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.3.22
CloudSecure WP Security v1.3.22
1.4.10 1.4.9 trunk 0.9.0 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.19 1.3.2 1.3.20 1.3.21 1.3.22 1.3.23 1.3.24 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
cloudsecure-wp-security / modules / two-factor-authentication.php
cloudsecure-wp-security / modules Last commit date
admin 7 months ago cli 8 months ago lib 11 months ago captcha.php 2 years ago cloudsecure-wp.php 6 months ago common.php 1 year ago config.php 2 years ago disable-access-system-file.php 1 year ago disable-author-query.php 2 years ago disable-login.php 1 year ago disable-restapi.php 7 months ago disable-xmlrpc.php 1 year ago htaccess.php 1 year ago login-log.php 1 year ago login-notification.php 1 year ago rename-login-page.php 1 year ago restrict-admin-page.php 10 months ago server-error-notification.php 1 year ago two-factor-authentication.php 6 months ago unify-messages.php 2 years ago update-notice.php 7 months ago waf-engine.php 7 months ago waf.php 1 year ago
two-factor-authentication.php
840 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class CloudSecureWP_Two_Factor_Authentication extends CloudSecureWP_Common {
8 private const KEY_FEATURE = 'two_factor_authentication';
9 private const OPTION_PREFIX = 'cloudsecurewp_2fa_data_';
10 private const SESSION_EXPIRY = 300;
11 private const CLEANUP_TIMEOUT = 60;
12 private const CLEANUP_BATCH_SIZE = 1000;
13
14 private $config;
15
16 /**
17 * @var CloudSecureWP_Disable_Login
18 */
19 private $disable_login;
20
21 /**
22 * @var CloudSecureWP_Login_Log
23 */
24 private $login_log;
25
26 function __construct( array $info, CloudSecureWP_Config $config, CloudSecureWP_Disable_Login $disable_login, CloudSecureWP_Login_Log $login_log ) {
27 parent::__construct( $info );
28 $this->config = $config;
29 $this->disable_login = $disable_login;
30 $this->login_log = $login_log;
31 }
32
33 /**
34 * 機能毎のKEY取得
35 *
36 * @return string
37 */
38 public function get_feature_key(): string {
39 return self::KEY_FEATURE;
40 }
41
42 /**
43 * 有効無効判定
44 *
45 * @return bool
46 */
47 public function is_enabled(): bool {
48 return $this->config->get( $this->get_feature_key() ) === 't';
49 }
50
51 /**
52 * 初期設定値取得
53 *
54 * @return array
55 */
56 public function get_default(): array {
57 return array( self::KEY_FEATURE => 'f' );
58 }
59
60 /**
61 * 設定値key取得
62 */
63 public function get_keys(): array {
64 return array( self::KEY_FEATURE );
65 }
66
67 /**
68 * 設定値取得
69 */
70 public function get_settings(): array {
71 $settings = array();
72 $keys = $this->get_keys();
73
74 foreach ( $keys as $key ) {
75 $settings[ $key ] = $this->config->get( $key );
76 }
77
78 return $settings;
79 }
80
81 /**
82 * 設定値保存
83 *
84 * @param array $settings
85 *
86 * @return void
87 */
88 public function save_settings( array $settings ): void {
89 $keys = $this->get_keys();
90
91 foreach ( $keys as $key ) {
92 $this->config->set( $key, $settings[ $key ] ?? '' );
93 }
94 $this->config->save();
95 }
96
97 /**
98 * 有効化
99 *
100 * @return void
101 */
102 public function activate(): void {
103 $settings = $this->get_default();
104 $this->save_settings( $settings );
105 }
106
107 /**
108 * 無効化
109 *
110 * @return void
111 */
112 public function deactivate(): void {
113 $settings = $this->get_settings();
114 $settings[ self::KEY_FEATURE ] = 'f';
115 $this->save_settings( $settings );
116 }
117
118 /**
119 * 管理画面上での有効無効判定
120 * 2段階認証の管理画面で「変更を保存」ボタンを押下時、
121 * is_enabled()のみを使うとデバイス登録のメニューが正しく表示されない。
122 *
123 * @return bool
124 */
125 public function is_enabled_on_screen(): bool {
126 if ( isset( $_POST['two_factor_authentication'] ) && ! empty( $_POST['two_factor_authentication'] ) ) {
127 return $this->check_environment() && sanitize_text_field( $_POST['two_factor_authentication'] ) === 't';
128 }
129
130 return $this->is_enabled();
131 }
132
133 /**
134 * 有効な権限グループに含まれるかどうか
135 *
136 * @param $role
137 *
138 * @return bool
139 */
140 private function is_role_enabled( $role ): bool {
141 return in_array( $role, get_option( 'cloudsecurewp_two_factor_authentication_roles', array() ) );
142 }
143
144 /**
145 * 2段階認証のエラーを出力
146 *
147 * @return void
148 */
149 private function login_error() {
150 if ( array_key_exists( 'google_authenticator_code', $_REQUEST ) ) {
151 if ( sanitize_text_field( $_REQUEST['google_authenticator_code'] ) ) {
152 $errors = '認証コードが間違っているか、有効期限が切れています。';
153 } else {
154 $errors = '認証コードが�
155 �力されていません。';
156 }
157 echo '<div id="login_error">' . esc_html( apply_filters( 'login_errors', $errors ) ) . "</div>\n";
158 }
159 }
160
161 /**
162 * 2段階認証のログインフォームを出力
163 *
164 * @param string $login_token
165 *
166 * @return void
167 */
168 private function login_form( $login_token ) {
169 ?>
170 <form name="loginform" id="loginform"
171 action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ); ?>" method="post">
172 <?php if ( array_key_exists( 'rememberme', $_REQUEST ) && 'forever' === sanitize_text_field( $_REQUEST['rememberme'] ) ) : ?>
173 <input name="rememberme" type="hidden" id="rememberme" value="forever"/>
174 <?php endif; ?>
175 <input type="hidden" name="login_token" value="<?php echo esc_attr( $login_token ); ?>">
176 <p>
177 <label for="google_authenticator_code">認証コード</label>
178 <input type="text" name="google_authenticator_code" id="google_authenticator_code" class="input"
179 value="" size="20" autocomplete="one-time-code"/>
180 </p>
181 <script type="text/javascript">document.getElementById("google_authenticator_code").focus();</script>
182 <p>デバイスのGoogle Authenticator アプリケーションに表示されている6桁の認証コードを�
183 �力してください。</p>
184 <p class="submit">
185 <?php wp_nonce_field( $this->get_feature_key() . '_csrf' ); ?>
186 <input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
187 value="<?php esc_attr_e( 'Log In' ); ?>"/>
188 <input type="hidden" name="redirect_to"
189 value="<?php echo esc_attr( sanitize_text_field( $_REQUEST['redirect_to'] ?? admin_url() ) ); ?>"/>
190 <input type="hidden" name="testcookie" value="1"/>
191 </p>
192 </form>
193 <?php
194 }
195
196 /**
197 * デバイス登録がまだのユーザーは、デバイス登録画面にリダイレクト
198 *
199 * @param $user_login
200 * @param $user
201 *
202 * @return void
203 * @noinspection PhpUnusedParameterInspection
204 */
205 public function redirect_if_not_two_factor_authentication_registered( $user_login, $user ) {
206 $secret = get_user_option( 'cloudsecurewp_two_factor_authentication_secret', $user->ID );
207
208 if ( isset( $user->roles[0] ) ) {
209 if ( $this->is_enabled() && $this->is_role_enabled( $user->roles[0] ) && ! $secret && $_SERVER['REQUEST_URI'] !== '/wp-admin/admin.php?page=cloudsecurewp_two_factor_authentication_registration' ) {
210 wp_redirect( admin_url( 'admin.php?page=cloudsecurewp_two_factor_authentication_registration' ) );
211 exit;
212 }
213 }
214 }
215
216 /**
217 * WordPress標準機能のユーザー一覧に表示するcolumnを追加
218 */
219 public function add_2factor_state_2user_list( $columns ) {
220 $new_columns = [];
221
222 foreach ( $columns as $key => $value ) {
223 $new_columns[ $key ] = $value;
224
225 if ( $key === 'role' ) {
226 $new_columns['is_2factor'] = '2段階認証';
227 }
228 }
229
230 return $new_columns;
231 }
232
233 /**
234 * WordPress標準機能のユーザー一覧に表示する二段階認証の設定状�
235 �を指定
236 */
237 public function show_2factor_state_2user_list( $value, $column_name, $user_id ) {
238 if ( $column_name === 'is_2factor' ) {
239 $value = get_user_meta( $user_id, 'wp_cloudsecurewp_two_factor_authentication_secret', true );
240 return $value !== '' ? '設定済' : '未設定';
241 }
242 return $value;
243 }
244
245 /**
246 * ユーザの2faシークレットキー取得
247 *
248 * @param int $user_id
249 *
250 * @return mixed
251 */
252 private function get_2fa_secret_key( int $user_id ) {
253 return get_user_option( 'cloudsecurewp_two_factor_authentication_secret', $user_id );
254 }
255
256 /**
257 * option keyを作成
258 *
259 * @param string $token
260 *
261 * @return string
262 */
263 private function create_option_key( string $token ): string {
264 return self::OPTION_PREFIX . $token;
265 }
266
267 /**
268 * option dataを登録
269 *
270 * @param string $key
271 * @param mixed $data
272 *
273 * @return void
274 */
275 private function set_option_data( string $key, $data ): void {
276 update_option( $key, $data, false );
277 }
278
279 /**
280 * option dataを取得
281 *
282 * @param string $key
283 *
284 * @return array|false データが存在しないまたは、有効期限切れの場合FALSEを返却
285 */
286 private function get_option_data( string $key ) {
287
288 $data = get_option( $key );
289
290 // データが存在しない
291 if ( ! $data || ! is_array( $data ) ) {
292 return false;
293 }
294
295 // 有効期限切れ
296 if ( ! isset( $data['expires'] ) || $data['expires'] <= time() ) {
297 return false;
298 }
299
300 // 有効なデータを返却
301 return $data;
302 }
303
304 /**
305 * option dataを削除
306 *
307 * @param string $key
308 *
309 * @return void
310 */
311 private function delete_option_data( string $key ): void {
312 delete_option( $key );
313 }
314
315 /**
316 * 2段階認証が�
317 要かどうか判定処理
318 *
319 * @param mixed $user
320 *
321 * @return bool
322 */
323 private function is_2fa_required( $user ): bool {
324
325 // 2段階認証が無効な場合
326 if ( ! $this->is_enabled() ) {
327 return false;
328 }
329
330 // 有効な権限グループに含まれない場合
331 if ( ! isset( $user->roles[0] ) || ! $this->is_role_enabled( $user->roles[0] ) ) {
332 return false;
333 }
334
335 // 2faシークレットキーが存在しない場合
336 if ( ! $this->get_2fa_secret_key( $user->ID ) ) {
337 return false;
338 }
339
340 return true;
341 }
342
343 /**
344 * 2段階認証画面を表示
345 *
346 * @param string $login_token
347 *
348 * @return void
349 */
350 private function show_two_factor_form( string $login_token ) {
351 // 2FA画面を表示
352 login_header( '2段階認証画面' );
353 $this->login_error();
354 $this->login_form( $login_token );
355 login_footer();
356 exit;
357 }
358
359 /**
360 * POSTデータから2FA関連の値を安�
361 �に取得
362 *
363 * @return array
364 */
365 private function get_2fa_post_data(): array {
366 return array(
367 'login_token' => sanitize_text_field( $_POST['login_token'] ?? '' ),
368 'google_authenticator_code' => sanitize_text_field( $_POST['google_authenticator_code'] ?? '' ),
369 );
370 }
371
372 /**
373 * 2段階認証コード検証処理
374 *
375 * @param int $user_id
376 * @param string $code
377 *
378 * @return bool
379 */
380 private function verify_2fa_code( int $user_id, string $code ): bool {
381
382 // 2faシークレットキー取得
383 $secret_key = $this->get_2fa_secret_key( $user_id );
384
385 // 2faシークレットキーが存在しない場合
386 if ( ! $secret_key ) {
387 return true;
388 }
389
390 // 2段階認証コードが有効な場合
391 if ( CloudSecureWP_Time_Based_One_Time_Password::verify_code( $secret_key, $code, 2 ) ) {
392 return true;
393 }
394
395 // 認証失敗
396 return false;
397 }
398
399 /**
400 * 認証フック: ユーザ名復�
401 �処理
402 *
403 * @param string $username
404 * @param bool $strict
405 *
406 * @return string
407 */
408 public function restore_login_name( string $username, $strict = false ): string {
409
410 // 初回アクセス・または初回認証の場合、何もしない
411 if ( ! isset( $_POST['google_authenticator_code'] ) ) {
412 return $username;
413 }
414
415 // 2FA関連のPOSTデータ取得
416 $post_data = $this->get_2fa_post_data();
417
418 // ログイン�
419 報を取得
420 $option_key = $this->create_option_key( $post_data['login_token'] );
421 $option_data = $this->get_option_data( $option_key );
422
423 // ログイン�
424 報が存在しない場合、何もしない
425 if ( $option_data === false ) {
426 return $username;
427 }
428
429 // ユーザ名を返却
430 return $option_data['user_login'];
431 }
432
433 /**
434 * 認証フック: ログインデータ復�
435 �処理
436 *
437 * @param mixed $user
438 * @param string $username
439 * @param string $password
440 *
441 * @return mixed
442 */
443 public function restore_login_session( $user, $username, $password ) {
444
445 // 初回アクセス・または初回認証の場合、何もしない
446 if ( ! isset( $_POST['google_authenticator_code'] ) ) {
447 return $user;
448 }
449
450 // CSRFトークンを検証(失敗すると「辿ったリンクは期限が切れています。」のエラー画面を表示し処理終了)
451 check_admin_referer( $this->get_feature_key() . '_csrf' );
452
453 // 2FA関連のPOSTデータ取得
454 $post_data = $this->get_2fa_post_data();
455
456 // ログイン�
457 報を取得
458 $option_key = $this->create_option_key( $post_data['login_token'] );
459 $option_data = $this->get_option_data( $option_key );
460
461 // ログインが有効期限切れの場合
462 // ログイン成功時にまとめてクリーンアップ処理を実行するため、ここでは消さない
463 if ( $option_data === false ) {
464 return new WP_Error( 'empty_username', 'セッションの有効期限が切れました。再度ログインしてください。' );
465 }
466
467 // ユーザーオブジェクト取得
468 $user = get_user_by( 'id', $option_data['user_id'] );
469 if ( ! $user ) {
470 $this->delete_option_data( $option_key );
471 return new WP_Error( 'empty_username', 'ユーザー�
472 報が見つかりません。再度ログインしてください。' );
473 }
474
475 // ログイン�
476 報のPOSTデータ復�
477
478 $_POST['log'] = $option_data['user_login'];
479
480 return $user;
481 }
482
483 /**
484 * 認証フック: 2段階認証処理
485 *
486 * @param mixed $user
487 * @param string $username
488 * @param string $password
489 *
490 * @return mixed
491 */
492 public function authenticate_with_two_factor( $user, $username, $password ) {
493
494 // 初回アクセス、または初回認証時
495 if ( ! isset( $_POST['google_authenticator_code'] ) ) {
496
497 // 認証失敗の場合
498 if ( is_wp_error( $user ) ) {
499 return $user;
500 }
501
502 // 2段階認証が不要な場合
503 if ( ! $this->is_2fa_required( $user ) ) {
504 return $user;
505 }
506
507 // option key生成
508 $session_token = bin2hex( random_bytes( 16 ) );
509 $option_key = $this->create_option_key( $session_token );
510
511 // 保存用認証データ作成
512 $option_data = array(
513 'user_id' => $user->ID,
514 'user_login' => sanitize_text_field( $_POST['log'] ?? '' ),
515 'expires' => time() + self::SESSION_EXPIRY,
516 'created' => time(),
517 );
518
519 // データを保存
520 $this->set_option_data( $option_key, $option_data );
521
522 // 2FA画面を表示して、処理終了
523 $this->show_two_factor_form( $session_token );
524 }
525
526 // 2FA関連のPOSTデータ取得
527 $post_data = $this->get_2fa_post_data();
528
529 // option key取得
530 $option_key = $this->create_option_key( $post_data['login_token'] );
531
532 // $userがWP_Errorの場合は処理をスキップ
533 if ( is_wp_error( $user ) ) {
534 return $user;
535 }
536
537 // 2段階認証成功の場合
538 if ( $this->verify_2fa_code( $user->ID, $post_data['google_authenticator_code'] ) ) {
539 $this->delete_option_data( $option_key );
540 return $user;
541 }
542
543 // ログイン失敗時の処理を実行(ログイン回数・ログインログ)
544 do_action( 'wp_login_failed', $_POST['log'], $user );
545
546 // 2FA画面を再表示して、処理終了
547 $this->show_two_factor_form( $post_data['login_token'] );
548 }
549
550 /**
551 * 2FAセッションデータを取得
552 *
553 * @param int $last_option_id
554 * @param int $limit
555 *
556 * @return array
557 */
558 private function fetch_2fa_sessions( int $last_option_id, int $limit ): array {
559 global $wpdb;
560
561 // セッションキーの接頭辞でLIKE検索
562 $like = $wpdb->esc_like( self::OPTION_PREFIX ) . '%';
563
564 // SQL実行(LIKE検索を行うため、意図的に直接クエリを実行する)
565 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
566 $options = $wpdb->get_results(
567 $wpdb->prepare(
568 "SELECT
569 option_id,
570 option_name,
571 option_value
572 FROM
573 {$wpdb->options}
574 WHERE TRUE
575 AND option_name LIKE %s
576 AND %d < option_id
577 ORDER BY
578 option_id ASC
579 LIMIT
580 %d",
581 $like,
582 $last_option_id,
583 $limit
584 )
585 );
586
587 return $options ?? array();
588 }
589
590 /**
591 * 期限切れセッションデータを収集
592 *
593 * @param array $options
594 *
595 * @return array ['log_data' => array, 'delete_option_names' => array]
596 */
597 private function collect_expired_session_data( array $options ): array {
598 // ログ登録用データリスト初期化
599 $log_data = array();
600 // 削除対象のoption_nameリスト
601 $delete_option_names = array();
602
603 foreach ( $options as $option ) {
604 // option_valueを連想�
605 �列に変換
606 $data = maybe_unserialize( $option->option_value );
607
608 // �
609 �列でない場合はスキップ
610 if ( ! is_array( $data ) ) {
611 continue;
612 }
613
614 // 有効期限が切れている場合
615 if ( isset( $data['expires'] ) && $data['expires'] <= time() ) {
616
617 // ログ登録用データを収集
618 $log_data[] = array(
619 'name' => $data['user_login'],
620 'ip' => $this->get_client_ip( '' ),
621 'status' => self::LOGIN_STATUS_FAILED,
622 'method' => 1,
623 'login_at' => wp_date( 'Y-m-d H:i:s', $data['created'] ), // WPのタイムゾーンに変更して登録
624 );
625
626 // 削除対象のoption_nameを収集
627 $delete_option_names[] = $option->option_name;
628 }
629 }
630
631 return array(
632 'log_data' => $log_data,
633 'delete_option_names' => $delete_option_names,
634 );
635 }
636
637 /**
638 * ログイン失敗ログを一括登録
639 * (呼び出し�
640 �でトランザクションを管理すること)
641 *
642 * @param array $log_data
643 *
644 * @return void
645 * @throws Exception SQLエラー発生時.
646 */
647 private function insert_login_failed_logs( array $log_data ): void {
648 if ( empty( $log_data ) ) {
649 return;
650 }
651
652 global $wpdb;
653
654 // プレースホルダーと値の準備
655 $values = array();
656 $placeholders = array();
657
658 // 収集したログデータを一括登録用に変換
659 foreach ( $log_data as $log ) {
660 $values[] = $log['name'];
661 $values[] = $log['ip'];
662 $values[] = $log['status'];
663 $values[] = $log['method'];
664 $values[] = $log['login_at'];
665 $placeholders[] = '(%s, %s, %d, %d, %s)';
666 }
667
668 // SQL実行(一括登録を行うため、意図的に直接クエリを実行する)
669 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
670 $result = $wpdb->query(
671 $wpdb->prepare(
672 "INSERT INTO `{$wpdb->prefix}cloudsecurewp_login_log`
673 (`name`, `ip`, `status`, `method`, `login_at`)
674 VALUES " . implode( ', ', $placeholders ),
675 $values
676 )
677 );
678
679 // SQLエラーチェック
680 if ( $result === false || ! empty( $wpdb->last_error ) ) {
681 throw new Exception( 'Failed to insert login logs: ' . $wpdb->last_error );
682 }
683 }
684
685 /**
686 * 指定されたオプションを一括削除
687 * (呼び出し�
688 �でトランザクションを管理すること)
689 *
690 * @param array $option_names
691 *
692 * @return void
693 * @throws Exception SQLエラー発生時.
694 */
695 private function delete_options( array $option_names ): void {
696 if ( empty( $option_names ) ) {
697 return;
698 }
699
700 global $wpdb;
701
702 // プレースホルダー作成
703 $placeholders = implode( ', ', array_fill( 0, count( $option_names ), '%s' ) );
704
705 // SQL実行(一括削除を行うため、意図的に直接クエリを実行する)
706 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
707 $result = $wpdb->query(
708 $wpdb->prepare(
709 "DELETE FROM
710 {$wpdb->options}
711 WHERE
712 option_name IN ($placeholders)",
713 $option_names
714 )
715 );
716
717 // SQLエラーチェック
718 if ( $result === false || ! empty( $wpdb->last_error ) ) {
719 throw new Exception( 'Failed to delete options: ' . $wpdb->last_error );
720 }
721 }
722
723 /**
724 * 期限切れログイン�
725 報セッションのクリーンアップ処理本体
726 *
727 * @return void
728 */
729 private function process_cleanup_expired_sessions(): void {
730 global $wpdb;
731
732 $last_option_id = 0;
733
734 while ( true ) {
735 try {
736 // 2FAセッションデータを取得
737 $options = $this->fetch_2fa_sessions( $last_option_id, self::CLEANUP_BATCH_SIZE );
738
739 // 取得するレコードがなくなったら終了
740 if ( empty( $options ) ) {
741 break;
742 }
743
744 // 最後に取得したoption_idを更新
745 $last_option_id = end( $options )->option_id;
746
747 // 期限切れセッションデータを収集
748 $result = $this->collect_expired_session_data( $options );
749 $log_data = $result['log_data'];
750 $delete_option_names = $result['delete_option_names'];
751
752 if ( empty( $delete_option_names ) && empty( $log_data ) ) {
753 continue;
754 }
755
756 // トランザクション開始
757 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
758 $wpdb->query( 'START TRANSACTION' );
759
760 // ログデータを一括登録
761 $this->insert_login_failed_logs( $log_data );
762
763 // オプションを一括削除
764 $this->delete_options( $delete_option_names );
765
766 // トランザクションコミット
767 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
768 $wpdb->query( 'COMMIT' );
769
770 } catch ( Exception $e ) {
771 // エラー発生時はロールバック
772 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
773 $wpdb->query( 'ROLLBACK' );
774 break;
775 }
776 }
777 }
778
779 /**
780 * 期限切れの2FAセッションをクリーンアップ
781 *
782 * @return void
783 */
784 public function cleanup_expired_sessions(): void {
785 global $wpdb;
786
787 // ロック名
788 $lock_name = 'cloudsecurewp_2fa_cleanup_lock';
789 // クリーンアップ処理の完了を�
790 つ最大秒数
791 $timeout = self::CLEANUP_TIMEOUT;
792
793 // ロックを取得
794 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
795 $get_lock = $wpdb->get_var(
796 $wpdb->prepare( 'SELECT GET_LOCK(%s, 0)', $lock_name )
797 );
798
799 if ( $get_lock === '1' ) {
800 // ロック取得成功(クリーンアップ実行�
801
802
803 try {
804 // クリーンアップ処理実行
805 $this->process_cleanup_expired_sessions();
806 } catch ( Exception $e ) {
807 // クリーンアップ処理実行で失敗しても�
808 部でロールバックするため、ここでは何もしない
809 } finally {
810 // ロック解放
811 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
812 $wpdb->query(
813 $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_name )
814 );
815 }
816 } else {
817 // ロック取得失敗(�
818 機�
819
820
821 // リーダーのクリーンアップ完了を�
822
823 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
824 $acquired_signal = $wpdb->get_var(
825 $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $lock_name, $timeout )
826 );
827
828 if ( $acquired_signal === '1' ) {
829 // ロック取得成功(クリーンアップ処理終了)
830 // 即座にロック解放(�
831 機完了の合図として使うだけ)
832 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
833 $wpdb->query(
834 $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_name )
835 );
836 }
837 }
838 }
839 }
840