PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.4.6
CloudSecure WP Security v1.4.6
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 / cli / commands / class-cloudsecure-wp-cli-enable.php
cloudsecure-wp-security / modules / cli / commands Last commit date
class-cloudsecure-wp-cli-disable-restapi.php 8 months ago class-cloudsecure-wp-cli-disable.php 8 months ago class-cloudsecure-wp-cli-enable.php 8 months ago class-cloudsecure-wp-cli-list.php 8 months ago class-cloudsecure-wp-cli-status.php 8 months ago
class-cloudsecure-wp-cli-enable.php
953 lines
1 <?php
2 /**
3 * CloudSecure WP Security Enable Command
4 * 機能の有効化用のコマンドクラス
5 *
6 * @package CloudSecure_WP_Security
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 if ( ! class_exists( 'WP_CLI' ) ) {
14 return;
15 }
16
17 // ベースクラスを読み込み
18 require_once dirname( __DIR__ ) . '/class-cloudsecure-wp-cli-base.php';
19
20 /**
21 * CloudSecure WP Security Enable Command
22 * 機能の有効化専用のコマンドクラス
23 */
24 class CloudSecureWP_CLI_Enable extends CloudSecureWP_CLI_Base {
25
26 /**
27 * @var array 現在処理中の機能�
28
29 */
30 private $current_feature = array();
31
32 /**
33 * @var array 表示用設定�
34
35 */
36 private $available_display_keys = null;
37
38 /**
39 * @var string WordPressロールバリデーション用エラーメッセージ
40 */
41 private $wordpress_roles_error_message;
42
43 /**
44 * 機能を有効化する
45 *
46 * ## EXAMPLES
47 * wp cldsec-wp-security enable disable-login
48 * wp cldsec-wp-security enable disable-login --interval=5 --duration=60
49 * wp cldsec-wp-security enable restrict-admin-page --paths="css,images"
50 * wp cldsec-wp-security enable login-notification --body="ログインしました__CLDSEC_NEWLINE__日時: {$date}"
51 *
52 * ## OPTIONS
53 * <feature> : 有効化する機能名(�
54 須)
55 * [--<setting>=<value>] : 設定値(オプション)
56 *
57 * @param array $args 位置引数
58 * @param array $assoc_args 関連�
59 �列引数
60 */
61 public function __invoke( $args = array(), $assoc_args = array() ) {
62 try {
63 // �
64 �通の機能検証処理
65 $validation_result = $this->validate_feature_arguments( $args, true );
66
67 if ( ! $validation_result['success'] ) {
68 $this->output_error_response( array( 'message' => $validation_result['error'] ) );
69 return;
70 }
71
72 // 機能�
73 報を�
74 �列にまとめて格納
75 $this->current_feature = array(
76 'name' => $validation_result['feature_name'],
77 'instance' => $validation_result['feature_instance'],
78 'description' => $validation_result['description'],
79 'key' => $validation_result['feature_instance']->get_feature_key(),
80 'valid_keys' => $this->get_valid_keys( $validation_result['feature_instance'], $validation_result['feature_name'] ),
81 );
82
83 // 表示用設定�
84 目を生成
85 $this->available_display_keys = $this->get_available_display_keys();
86
87 // 現在の設定値を取得し新しい設定値を準備
88 $current_settings = $this->current_feature['instance']->get_settings();
89 $new_settings = $current_settings;
90
91 // 機能を有効化
92 $new_settings[ $this->current_feature['key'] ] = 't';
93
94 // 追加設定値を処理
95 if ( ! empty( $assoc_args ) ) {
96 // 1. 設定�
97 目の存在確認
98 $validation_result = $this->validate_setting_keys( $assoc_args );
99 if ( ! $validation_result['success'] ) {
100 $this->output_error_response( array( 'message' => $validation_result['error'] ) );
101 return;
102 }
103
104 // 2. 設定値のバリデーション
105 $validation_result = $this->validate_setting_values( $assoc_args );
106 if ( ! $validation_result['success'] ) {
107 $this->output_error_response( array( 'message' => $validation_result['error'] ) );
108 return;
109 }
110
111 // 3. 設定値を変換して新しい設定値を作成
112 $converted_settings = $this->convert_settings_for_storage( $validation_result['processed_settings'] );
113
114 // 検証済みの設定値をマージ
115 $new_settings = array_merge( $new_settings, $converted_settings );
116 }
117
118 // 設定を保存
119 $activation_result = $this->activate_feature( $new_settings, $current_settings );
120
121 if ( $activation_result['success'] ) {
122 // 有効化後の設定値を取得
123 $updated_settings = $this->current_feature['instance']->get_settings();
124 $enabled = isset( $updated_settings[ $this->current_feature['key'] ] ) && $updated_settings[ $this->current_feature['key'] ] === 't';
125 $status = $enabled ? 'enabled' : 'disabled';
126 $configuration = array();
127 foreach ( $updated_settings as $key => $value ) {
128 if ( $key !== $this->current_feature['key'] ) {
129 $display_key = $this->storage_key_to_display_key( $key );
130
131 // �
132 部設定は除外
133 if ( $this->is_internal_setting( $this->current_feature['name'], $display_key ) ) {
134 continue;
135 }
136
137 // テキストエリア�
138 容の改行コード変換
139 if ( $this->is_textarea_setting( $this->current_feature['name'], $display_key ) ) {
140 $value = $this->convert_textarea_to_placeholder( $value );
141 }
142
143 // 表示用の値を正規化(boolean: t→1, f→0、ビットフラグ: 63→sql,xss,command,code,mail)
144 $value = $this->normalize_value_for_display( $this->current_feature['name'], $display_key, $value );
145
146 $configuration[ $display_key ] = $value;
147 }
148 }
149
150 // 特別な設定を追加(WordPressオプションを使用するものと2段階認証のユーザー設定状況)
151 $this->add_special_configuration_settings( $this->current_feature['name'], $configuration );
152
153 // 成功メッセージを生成
154 $success_message = $this->generate_success_message( $new_settings );
155
156 // 成功レスポンス
157 $response_data = array(
158 'result' => 'success',
159 'data' => array(
160 'message' => $success_message,
161 'status' => $status,
162 'configuration' => $configuration,
163 ),
164 );
165
166 $this->output_success_response( $response_data );
167 } else {
168 $this->output_error_response(
169 array(
170 'message' => "機能 '{$this->current_feature['name']}' ({$this->current_feature['description']}) の有効化に失敗しました。",
171 'error_details' => $activation_result['error'] ?? '詳細不明',
172 )
173 );
174 }
175 } catch ( Exception $e ) {
176 $this->output_error_response( array( 'message' => 'コマンド実行中にエラーが発生しました: ' . $e->getMessage() ) );
177 }
178 }
179
180 /**
181 * 機能インスタンスから有効な設定キーを取得
182 *
183 * @param object $feature_instance 機能インスタンス
184 * @param string $feature_name 機能名
185 * @return array 有効な設定キーの�
186 �列
187 */
188 private function get_valid_keys( $feature_instance, $feature_name ) {
189 $valid_keys = array_keys( $feature_instance->get_default() );
190
191 // 特別処理が�
192 要な機能の場合のみ特別なキーを追加
193 if ( in_array( $feature_name, self::SPECIAL_PROCESSING_FEATURES, true ) ) {
194 switch ( $feature_name ) {
195 case 'two-factor-authentication':
196 $valid_keys[] = 'enabled_roles';
197 break;
198
199 case 'server-error-notification':
200 $valid_keys[] = 'email_notification';
201 break;
202 }
203 }
204
205 return $valid_keys;
206 }
207
208 /**
209 * 表示用の利用可能設定�
210 目一覧を生成
211 *
212 * @return array
213 */
214 private function get_available_display_keys() {
215 $available_keys = array();
216 foreach ( $this->current_feature['valid_keys'] as $valid_key ) {
217
218 if ( $valid_key !== $this->current_feature['key'] ) { // 機能キー自体は除外
219 $display_key = $this->storage_key_to_display_key( $valid_key );
220
221 // �
222 部設定は除外
223 if ( ! $this->is_internal_setting( $this->current_feature['name'], $display_key ) ) {
224 $available_keys[] = $display_key;
225 }
226 }
227 }
228
229 return $available_keys;
230 }
231
232 /**
233 * 表示用キーから保存用キーに変換
234 *
235 * @param string $display_key 表示用キー(例: 'interval')
236 * @return string 保存用キー(例: 'cloudsecurewp_disable_login_interval')
237 */
238 private function display_key_to_storage_key( $display_key ) {
239 // 特別設定の場合はそのまま返す
240 if ( $this->is_special_setting_key( $display_key ) ) {
241 return $display_key;
242 }
243
244 // 通常設定の場合は機能プレフィックスを付加
245 return $this->current_feature['key'] . '_' . $display_key;
246 }
247
248 /**
249 * 保存用キーから表示用キーに変換
250 *
251 * @param string $storage_key 保存用キー(例: 'cloudsecurewp_disable_login_interval')
252 * @return string 表示用キー(例: 'interval')
253 */
254 private function storage_key_to_display_key( $storage_key ) {
255 // 特別設定の場合はそのまま返す
256 if ( $this->is_special_setting_key( $storage_key ) ) {
257 return $storage_key;
258 }
259
260 // 通常設定の場合は機能プレフィックスを除去
261 $prefix = $this->current_feature['key'] . '_';
262
263 if ( strpos( $storage_key, $prefix ) === 0 ) {
264 return substr( $storage_key, strlen( $prefix ) );
265 }
266
267 return $storage_key;
268 }
269
270 /**
271 * 特別設定キーかどうかを判定
272 *
273 * @param string $key 設定キー
274 * @return bool 特別設定の場合true
275 */
276 private function is_special_setting_key( $key ) {
277 return in_array( $key, array_values( self::SPECIAL_SETTING_KEYS ), true );
278 }
279
280 /**
281 * 設定�
282 目の存在確認
283 *
284 * @param array $input_settings �
285 �力された設定値
286 * @return array バリデーション結果
287 */
288 private function validate_setting_keys( $input_settings ) {
289 foreach ( $input_settings as $key => $value ) {
290 // 機能の設定�
291 報と比較のため保存用キーに変換
292 $target_key = $this->display_key_to_storage_key( $key );
293
294 // 有効な設定キーかチェック
295 if ( ! in_array( $target_key, $this->current_feature['valid_keys'], true ) ) {
296 $available_list = empty( $this->available_display_keys ) ? 'なし' : implode( ', ', $this->available_display_keys );
297
298 return array(
299 'success' => false,
300 'error' => "機能 '{$this->current_feature['name']}' には設定�
301 目 '{$key}' は存在しません。利用可能な設定�
302 : {$available_list}",
303 );
304 }
305 }
306
307 return array( 'success' => true );
308 }
309
310 /**
311 * 設定値のバリデーション
312 *
313 * @param array $input_settings �
314 �力された設定値
315 * @return array バリデーション結果(成功時は処理済み設定値も含む)
316 */
317 private function validate_setting_values( $input_settings ) {
318 $processed_settings = array();
319
320 foreach ( $input_settings as $key => $value ) {
321 $converted_value = (string) $value;
322
323 // WordPress管理画面と同様のサニタイズ処理を適用
324 $converted_value = $this->sanitize_setting_value( $this->current_feature['name'], $key, $converted_value );
325
326 // 改行コードの変換
327 if ( $this->is_textarea_setting( $this->current_feature['name'], $key ) ) {
328 $converted_value = $this->convert_placeholder_to_textarea( $converted_value );
329 }
330
331 // �
332 �列系設定の処理
333 if ( $this->is_array_setting( $this->current_feature['name'], $key ) || $this->is_bitflag_setting( $this->current_feature['name'], $key ) ) {
334 $converted_value = $this->convert_string_to_array( $converted_value );
335 }
336
337 // バリデーション実行
338 $validation_result = $this->validate_setting_value( $key, $converted_value );
339 if ( ! $validation_result['success'] ) {
340 return array(
341 'success' => false,
342 'error' => $validation_result['error'],
343 );
344 }
345
346 // 処理済み設定値を保存
347 $processed_settings[ $key ] = $converted_value;
348 }
349
350 return array(
351 'success' => true,
352 'processed_settings' => $processed_settings,
353 );
354 }
355
356 /**
357 * 設定値を保存用形式に変換
358 *
359 * @param array $processed_settings 処理済み設定値
360 * @return array 変換済み設定値
361 */
362 private function convert_settings_for_storage( $processed_settings ) {
363 $converted_settings = array();
364
365 foreach ( $processed_settings as $key => $value ) {
366 if ( $this->is_special_setting_key( $key ) ) {
367 $converted_value = $this->normalize_value_for_input( $this->current_feature['name'], $key, $value );
368 $converted_settings[ $key ] = $converted_value;
369
370 } else {
371 // 通常設定の場合は機能プレフィックスを付加
372 $full_key = $this->display_key_to_storage_key( $key );
373 $converted_value = $this->normalize_value_for_input( $this->current_feature['name'], $key, $value );
374 $converted_settings[ $full_key ] = $converted_value;
375 }
376 }
377
378 return $converted_settings;
379 }
380
381 /**
382 * 機能を有効化する
383 *
384 * @param array $settings 新しい設定値
385 * @param array $old_data �
386 �の設定値(ロールバック用)
387 * @return array 実行結果
388 */
389 private function activate_feature( $settings, $old_data ) {
390 try {
391 // 設定を保存
392 if ( method_exists( $this->current_feature['instance'], 'save_settings' ) ) {
393 $this->current_feature['instance']->save_settings( $settings );
394 } else {
395 return array(
396 'success' => false,
397 'error' => 'save_settingsメソッドが見つかりません。',
398 );
399 }
400
401 // htaccessを使用する機能の場合は更新処理を実行
402 if ( in_array( $this->current_feature['name'], self::HTACCESS_FEATURES, true ) ) {
403 $update_result = $this->handle_htaccess_feature_activation( $settings, $old_data );
404 if ( ! $update_result['success'] ) {
405 return $update_result;
406 }
407 }
408
409 // 特別なWordPressオプション保存が�
410 要な機能の場合のみ実行
411 if ( in_array( $this->current_feature['name'], self::SPECIAL_PROCESSING_FEATURES, true ) ) {
412 $special_result = $this->handle_special_feature_settings( $settings, $old_data );
413 if ( ! $special_result['success'] ) {
414 return array(
415 'success' => false,
416 'error' => $special_result['error'],
417 );
418 }
419 }
420
421 return array(
422 'success' => true,
423 );
424
425 } catch ( Exception $e ) {
426 return array(
427 'success' => false,
428 'error' => $e->getMessage(),
429 );
430 }
431 }
432
433 /**
434 * htaccess使用機能の有効化処理
435 *
436 * @param array $settings 新しい設定値
437 * @param array $old_data 古い設定値
438 * @return array 実行結果
439 */
440 private function handle_htaccess_feature_activation( $settings, $old_data ) {
441 try {
442 switch ( $this->current_feature['name'] ) {
443 case 'restrict-admin-page':
444 if ( method_exists( $this->current_feature['instance'], 'update' ) ) {
445 if ( ! $this->current_feature['instance']->update() ) {
446 $this->rollback_settings( $old_data );
447 return array(
448 'success' => false,
449 'error' => 'htaccessの更新に失敗しました。機能を無効にしました。',
450 );
451 }
452 }
453 break;
454
455 case 'rename-login-page':
456 if ( method_exists( $this->current_feature['instance'], 'update_htaccess' ) ) {
457 if ( ! $this->current_feature['instance']->update_htaccess() ) {
458 $this->rollback_settings( $old_data );
459 return array(
460 'success' => false,
461 'error' => 'htaccessの更新に失敗しました。機能を無効にしました。',
462 );
463 }
464 }
465 break;
466
467 case 'disable-xmlrpc':
468 $type_key = $this->current_feature['key'] . '_type';
469
470 if ( ! isset( $settings[ $type_key ] ) ) {
471 $this->rollback_settings( $old_data );
472 return array(
473 'success' => false,
474 'error' => 'XML-RPC無効化機能の無効化タイプ設定が見つかりません。',
475 );
476 }
477
478 $type_value = $settings[ $type_key ];
479
480 if ( $type_value === '1' ) {
481 if ( method_exists( $this->current_feature['instance'], 'remove_htaccess' ) ) {
482 if ( ! $this->current_feature['instance']->remove_htaccess() ) {
483 $this->rollback_settings( $old_data );
484 return array(
485 'success' => false,
486 'error' => 'htaccessの更新に失敗しました。機能を無効にしました。',
487 );
488 }
489 }
490 } elseif ( $type_value === '2' ) {
491 if ( method_exists( $this->current_feature['instance'], 'update_htaccess' ) ) {
492 if ( ! $this->current_feature['instance']->update_htaccess() ) {
493 $this->rollback_settings( $old_data );
494 return array(
495 'success' => false,
496 'error' => 'htaccessの更新に失敗しました。機能を無効にしました。',
497 );
498 }
499 }
500 }
501 break;
502 }
503
504 return array( 'success' => true );
505
506 } catch ( Exception $e ) {
507 $this->rollback_settings( $old_data );
508 return array(
509 'success' => false,
510 'error' => 'htaccess更新中にエラーが発生しました: ' . $e->getMessage() . ' 機能を無効にしました。',
511 );
512 }
513 }
514
515 /**
516 * 設定のロールバック処理(機能を無効化)
517 *
518 * @param array $old_data �
519 �の設定値
520 */
521 private function rollback_settings( $old_data ) {
522 try {
523 // 機能を無効にした設定値を作成
524 $disabled_settings = $old_data;
525 $disabled_settings[ $this->current_feature['key'] ] = 'f';
526
527 $this->current_feature['instance']->save_settings( $disabled_settings );
528 } catch ( Exception $e ) {
529 // ロールバック中にエラーが発生した場合は警告として出力
530 WP_CLI::warning( 'ロールバック中にエラーが発生しました: ' . $e->getMessage() );
531 }
532 }
533
534 /**
535 * 特別な機能の設定処理
536 *
537 * @param array $settings
538 * @param array $old_data �
539 �の設定値(ロールバック用)
540 * @return array 処理結果
541 */
542 private function handle_special_feature_settings( $settings, $old_data ) {
543 // 特別設定を抽出
544 $special_settings = $this->process_special_settings( $settings );
545
546 // 特別設定をWordPressオプションとして保存
547 if ( ! empty( $special_settings ) ) {
548 $save_results = $this->save_special_settings( $special_settings );
549
550 // 失敗した設定があるかチェック
551 $failed_settings = array();
552 foreach ( $save_results as $key => $result ) {
553 if ( ! $result['success'] ) {
554 $failed_settings[] = $key . ': ' . $result['message'];
555 }
556 }
557
558 if ( ! empty( $failed_settings ) ) {
559 // htaccess処理と同様に機能を無効化
560 $this->rollback_settings( $old_data );
561 return array(
562 'success' => false,
563 'error' => '設定の保存に失敗しました。機能を無効にしました: ' . implode( ', ', $failed_settings ),
564 );
565 }
566 }
567
568 return array( 'success' => true );
569 }
570
571 /**
572 * 特別設定値をWordPressオプションとして保存
573 *
574 * @param array $special_settings 特別設定の�
575 �列
576 * @return array 保存結果の�
577 �列
578 */
579 private function save_special_settings( $special_settings ) {
580 // WordPress関数の存在チェック
581 if ( ! function_exists( 'update_option' ) ) {
582 $results = array();
583 foreach ( $special_settings as $key => $value ) {
584 $results[ $key ] = array(
585 'success' => false,
586 'message' => 'update_option関数が利用できません',
587 );
588 }
589 return $results;
590 }
591
592 $option_mapping = array(
593 self::SPECIAL_SETTING_KEYS['TWO_FACTOR_ENABLED_ROLES'] => self::WORDPRESS_OPTION_NAMES['TWO_FACTOR_ROLES'],
594 self::SPECIAL_SETTING_KEYS['TWO_FACTOR_USER_REGISTRATIONS'] => self::WORDPRESS_OPTION_NAMES['TWO_FACTOR_REGISTRATIONS'],
595 self::SPECIAL_SETTING_KEYS['SERVER_ERROR_EMAIL_NOTIFICATION'] => self::WORDPRESS_OPTION_NAMES['SERVER_ERROR_EMAIL'],
596 );
597
598 $results = array();
599
600 foreach ( $special_settings as $key => $value ) {
601 if ( ! isset( $option_mapping[ $key ] ) ) {
602 $results[ $key ] = array(
603 'success' => false,
604 'message' => "未知の特別設定キー: {$key}",
605 );
606 continue;
607 }
608
609 $option_name = $option_mapping[ $key ];
610
611 // 現在の値を事前にチェック
612 $current_value = get_option( $option_name, null );
613 if ( $current_value === $value ) {
614 // 値が同じ場合はupdate_optionを実行せずに成功として扱う
615 $results[ $key ] = array(
616 'success' => true,
617 'message' => "WordPressオプション '{$option_name}' は既に設定済みです",
618 );
619 continue;
620 }
621
622 // 値が異なる場合のみupdate_optionを実行
623 $result = update_option( $option_name, $value );
624
625 if ( $result !== false ) {
626 $results[ $key ] = array(
627 'success' => true,
628 'message' => "WordPressオプション '{$option_name}' を更新しました",
629 );
630
631 } else {
632 $results[ $key ] = array(
633 'success' => false,
634 'message' => "WordPressオプション '{$option_name}' の更新に失敗しました",
635 );
636 }
637 }
638
639 return $results;
640 }
641
642 /**
643 * 特別設定の処理を実行
644 *
645 * @param array $settings バリデーション済みの設定値
646 * @return array 特別設定値の�
647 �列
648 */
649 private function process_special_settings( $settings ) {
650 $special_settings = array();
651
652 foreach ( $settings as $key => $value ) {
653 if ( in_array( $key, array_values( self::SPECIAL_SETTING_KEYS ), true ) ) {
654 $special_settings[ $key ] = $value;
655 }
656 }
657
658 return $special_settings;
659 }
660
661 /**
662 * 設定値のバリデーション
663 *
664 * @param string $setting_key 設定キー
665 * @param mixed $value 設定値
666 * @return array バリデーション結果
667 */
668 private function validate_setting_value( $setting_key, $value ) {
669 // カスタムロジックが�
670 要なバリデーション
671 $custom_rules = $this->get_custom_validation_rules( $setting_key );
672
673 if ( $custom_rules ) {
674 return $this->apply_validation_rule( $custom_rules, $value );
675 }
676
677 // 機能インスタンスから定数設定を使用したバリデーション
678 if ( method_exists( $this->current_feature['instance'], 'get_constant_settings' ) ) {
679 $constant_rules = $this->get_constant_based_validation_rules( $setting_key );
680 if ( $constant_rules ) {
681 return $this->apply_validation_rule( $constant_rules, $value );
682 }
683 }
684
685 WP_CLI::debug( "No specific validation rules for setting '{$setting_key}'. Skipping validation." );
686
687 // どちらにも該当しない場合は成功
688 return array( 'success' => true );
689 }
690
691 /**
692 * 定数設定に基づくバリデーションルールを取得
693 *
694 * @param string $setting_key
695 * @return array|null
696 */
697 private function get_constant_based_validation_rules( $setting_key ) {
698 $constant_settings = $this->current_feature['instance']->get_constant_settings();
699 $full_key = $this->display_key_to_storage_key( $setting_key );
700
701 if ( isset( $constant_settings[ $full_key ] ) && is_array( $constant_settings[ $full_key ] ) ) {
702 // 許可値を文字列型に変換(コマンドライン�
703 �力は文字列のため)
704 $allowed_values = array_map( 'strval', $constant_settings[ $full_key ] );
705
706 return array(
707 'type' => 'select',
708 'allowed_values' => $allowed_values,
709 'error_message' => "{$setting_key}は次のいずれかの値を指定してください: " . implode( ', ', $allowed_values ),
710 );
711 }
712
713 return null;
714 }
715
716 /**
717 * カスタムロジックが�
718 要なバリデーションルールを取得
719 *
720 * @param string $setting_key
721 * @return array|null
722 */
723 private function get_custom_validation_rules( $setting_key ) {
724 $static_rules = array(
725 'rename-login-page' => array(
726 'name' => array(
727 'type' => 'custom',
728 'validator' => 'validate_login_name',
729 'error_message' => '変更後のログインURLは半角英小文字、半角数字、ハイフン、アンダースコアのいずれかを使用し、4文字以上12文字以下で指定してください。',
730 ),
731 'disable_redirect' => array(
732 'type' => 'select',
733 'allowed_values' => array( '0', '1' ),
734 'error_message' => 'リダイレクト設定には 0 または 1 のいずれかの値を指定してください。',
735 ),
736 ),
737 'login-notification' => array(
738 'admin_only' => array(
739 'type' => 'select',
740 'allowed_values' => array( '0', '1' ),
741 'error_message' => '受信�
742 設定には 0 または 1 のいずれかの値を指定してください。',
743 ),
744 ),
745 'server-error-notification' => array(
746 'email_notification' => array(
747 'type' => 'select',
748 'allowed_values' => array( '0', '1' ),
749 'error_message' => '通知設定には 0 または 1 のいずれかの値を指定してください。',
750 ),
751 ),
752 'two-factor-authentication' => array(
753 'enabled_roles' => array(
754 'type' => 'custom',
755 'validator' => 'validate_wordpress_roles',
756 'error_message' => '2段階認証が有効な権限グループには有効なWordPressユーザー権限を指定してください。',
757 ),
758 ),
759 'waf' => array(
760 'available_rules' => array(
761 'type' => 'custom',
762 'validator' => 'validate_waf_bitflag_strings',
763 'error_message' => '検知する攻撃種別には有効な攻撃タイプを指定してください。利用可能な値: ' . implode( ', ', self::WAF_ATTACK_TYPES ),
764 ),
765 ),
766 );
767
768 if ( isset( $static_rules[ $this->current_feature['name'] ][ $setting_key ] ) ) {
769 return $static_rules[ $this->current_feature['name'] ][ $setting_key ];
770 }
771
772 return null;
773 }
774
775 /**
776 * バリデーションルールを適用
777 *
778 * @param array $rule
779 * @param mixed $value
780 * @return array
781 */
782 private function apply_validation_rule( $rule, $value ) {
783 switch ( $rule['type'] ) {
784 case 'select':
785 if ( ! in_array( $value, $rule['allowed_values'], true ) ) {
786 return array(
787 'success' => false,
788 'error' => $rule['error_message'],
789 );
790 }
791 break;
792
793 case 'custom':
794 if ( isset( $rule['validator'] ) && method_exists( $this, $rule['validator'] ) ) {
795 $validator_method = $rule['validator'];
796 if ( ! $this->$validator_method( $value ) ) {
797 // WordPressロールバリデーションの場合、カスタムエラーメッセージを使用
798 if ( $validator_method === 'validate_wordpress_roles' && ! empty( $this->wordpress_roles_error_message ) ) {
799 $error_message = $this->wordpress_roles_error_message;
800 $this->wordpress_roles_error_message = null; // リセット
801 } else {
802 $error_message = $rule['error_message'];
803 }
804
805 return array(
806 'success' => false,
807 'error' => $error_message,
808 );
809 }
810 }
811 break;
812 }
813
814 return array( 'success' => true );
815 }
816
817 /**
818 * ログインページ名のバリデーション
819 *
820 * @param string $name
821 * @return bool
822 */
823 private function validate_login_name( $name ) {
824 // 4文字以上12文字以下
825 if ( strlen( $name ) < 4 || strlen( $name ) > 12 ) {
826 return false;
827 }
828
829 // 半角英小文字、半角数字、ハイフン、アンダースコアのみ
830 if ( ! preg_match( '/^[a-z0-9_-]+$/', $name ) ) {
831 return false;
832 }
833
834 return true;
835 }
836
837 /**
838 * WordPressロールのバリデーション
839 *
840 * @param array $roles
841 * @return bool
842 */
843 private function validate_wordpress_roles( $roles ) {
844 // WordPress関数の存在チェック
845 if ( ! function_exists( 'wp_roles' ) ) {
846 $this->wordpress_roles_error_message = 'wp_roles関数が利用できません';
847 return false;
848 }
849
850 // WordPressから動的にロール一覧を取得(管理画面と同様の処理)
851 $wp_roles_obj = wp_roles();
852 if ( ! $wp_roles_obj || ! isset( $wp_roles_obj->roles ) ) {
853 $this->wordpress_roles_error_message = 'WordPressロール�
854 報を取得できませんでした';
855 return false;
856 }
857
858 $valid_roles = array_keys( $wp_roles_obj->roles );
859
860 // �
861 �列でない場合は単一値として扱う
862 if ( ! is_array( $roles ) ) {
863 $roles = array( $roles );
864 }
865
866 // 各ロールが有効かチェック
867 foreach ( $roles as $role ) {
868 if ( ! in_array( $role, $valid_roles, true ) ) {
869 // 無効なロールが見つかった場合、エラーメッセージに有効なロール一覧を含める
870 $this->wordpress_roles_error_message = 'enabled_rolesには有効なWordPressユーザー権限を指定してください。有効な権限: ' . implode( ', ', $valid_roles ) . "。無効な値: {$role}";
871 return false;
872 }
873 }
874
875 return true;
876 }
877
878 /**
879 * WAFビットフラグ文字列のバリデーション
880 *
881 * @param array $value
882 * @return bool
883 */
884 private function validate_waf_bitflag_strings( $value ) {
885 // �
886 �列でない場合は無効
887 if ( ! is_array( $value ) ) {
888 return false;
889 }
890
891 // 空�
892 �列の場合は有効(0値として扱う)
893 if ( empty( $value ) ) {
894 return true;
895 }
896
897 // 各文字列が有効かチェック
898 foreach ( $value as $string ) {
899 if ( ! empty( $string ) && ! in_array( $string, self::WAF_ATTACK_TYPES, true ) ) {
900 return false;
901 }
902 }
903
904 return true;
905 }
906
907 /**
908 * 成功メッセージを生成する
909 *
910 * @param array $settings 設定値
911 * @return string 成功メッセージ
912 */
913 private function generate_success_message( array $settings ): string {
914 // disable-xmlrpcの場合は特別処理
915 if ( $this->current_feature['name'] === 'disable-xmlrpc' ) {
916 return $this->generate_disable_xmlrpc_message( $settings );
917 }
918
919 // その他の機能は統一フォーマット
920 return $this->current_feature['description'] . '機能が有効になりました。';
921 }
922
923 /**
924 * disable-xmlrpc機能の成功メッセージを生成する
925 *
926 * @param array $settings 設定値
927 * @return string 成功メッセージ
928 */
929 private function generate_disable_xmlrpc_message( array $settings ): string {
930 // disable-xmlrpc機能インスタンスから定数設定を取得
931 if ( method_exists( $this->current_feature['instance'], 'get_constant_settings' ) ) {
932 $constant_settings = $this->current_feature['instance']->get_constant_settings();
933 $full_type_key = $this->current_feature['key'] . '_type';
934 $disable_xmlrpc_type = $settings[$full_type_key] ?? '';
935
936 // 無効化種別に応じてメッセージを分岐
937 if ( isset( $constant_settings['disable_xmlrpc_type'] ) ) {
938 $type_values = $constant_settings['disable_xmlrpc_type'];
939
940 if ( $disable_xmlrpc_type === $type_values[0] ) {
941 // ピンバック無効
942 return 'ピンバック無効化機能が有効になりました。';
943 } elseif ( $disable_xmlrpc_type === $type_values[1] ) {
944 // XML-RPC無効
945 return 'XML-RPC無効化機能が有効になりました。';
946 }
947 }
948 }
949
950 return $this->current_feature['description'] . '機能が有効になりました。';
951 }
952 }
953