| @@ -8,13 +8,12 @@ | ||
| 8 | 8 | namespace WP_Defender\Controller; |
| 9 | 9 | |
| 10 | 10 | use Countable; |
| 11 | 11 | use WP_Defender\Event; |
| 12 | -use WP_Defender\Admin; | |
| 13 | 12 | use Calotes\Component\Request; |
| 14 | 13 | use Calotes\Component\Response; |
| 15 | 14 | use Calotes\Helper\Array_Cache; |
| 16 | -use WP_Defender\Component\Rate; | |
| 15 | +use WP_Defender\Behavior\WPMUDEV; | |
| 17 | 16 | use WP_Defender\Model\Notification\Tweak_Reminder; |
| 18 | 17 | use WP_Defender\Component\Config\Config_Hub_Helper; |
| 19 | 18 | use WP_Defender\Component\Security_Tweaks\Hide_Error; |
| 20 | 19 | use WP_Defender\Component\Security_Tweaks\WP_Version; |
| @@ -73,13 +72,33 @@ | ||
| 73 | 72 | |
| 74 | 73 | public const STATUS_ISSUES = 'issues', STATUS_RESOLVE = 'fixed', STATUS_IGNORE = 'ignore', STATUS_RESTORE = 'restore'; |
| 75 | 74 | |
| 76 | 75 | /** |
| 76 | + * Maps tweak slugs to frontend category identifiers. | |
| 77 | + * | |
| 78 | + * @var array<string,string> | |
| 79 | + */ | |
| 80 | + private const TWEAK_CATEGORIES = array( | |
| 81 | + 'disable-xml-rpc' => 'network', | |
| 82 | + 'wp-version' => 'updates', | |
| 83 | + 'hide-error' => 'error', | |
| 84 | + 'php-version' => 'updates', | |
| 85 | + 'replace-admin-username' => 'auth', | |
| 86 | + 'security-key' => 'updates', | |
| 87 | + 'login-duration' => 'auth', | |
| 88 | + 'disable-trackback' => 'network', | |
| 89 | + 'prevent-enum-users' => 'error', | |
| 90 | + 'disable-file-editor' => 'access', | |
| 91 | + 'protect-information' => 'error', | |
| 92 | + 'prevent-php-executed' => 'access', | |
| 93 | + ); | |
| 94 | + | |
| 95 | + /** | |
| 77 | 96 | * Initializes the model and service, registers routes, and sets up scheduled events if the model is active. |
| 78 | 97 | */ |
| 79 | 98 | public function __construct() { |
| 80 | 99 | $this->register_page( |
| 81 | - esc_html__( 'Recommendations', 'defender-security' ), | |
| 100 | + esc_html__( 'Hardening', 'defender-security' ), | |
| 82 | 101 | $this->slug, |
| 83 | 102 | array( $this, 'main_view' ), |
| 84 | 103 | $this->parent_slug |
| 85 | 104 | ); |
| @@ -102,19 +121,20 @@ | ||
| 102 | 121 | /** |
| 103 | 122 | * Dummy function for testing a check. |
| 104 | 123 | */ |
| 105 | 124 | public function should_output_error() { |
| 106 | - if ( empty( defender_get_data_from_request( 'defender_test_error_reporting', 'g' ) ) ) { | |
| 125 | + $test_err_reporting = defender_get_data_from_request( 'defender_test_error_reporting', 'g' ); | |
| 126 | + if ( ! is_string( $test_err_reporting ) || '' === trim( $test_err_reporting ) ) { | |
| 107 | 127 | return; |
| 108 | 128 | } |
| 109 | - // It should be only trigger by admin. | |
| 129 | + // It should be triggered only by admin. | |
| 110 | 130 | if ( ! $this->check_permission() ) { |
| 111 | 131 | return; |
| 112 | 132 | } |
| 113 | 133 | |
| 114 | - $var = '$' . uniqid( '', true ); | |
| 115 | - // This should output a warning. Ignored phpcs as it's a test. | |
| 116 | - echo ${$var}; // phpcs:ignore | |
| 134 | + // This should output a warning. Ignored by PHPCS as it's a test. | |
| 135 | + // @phpstan-ignore-next-line. | |
| 136 | + echo $undefined_variable; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Intentionally outputs an undefined variable to test warning handling. | |
| 117 | 137 | exit(); |
| 118 | 138 | } |
| 119 | 139 | |
| 120 | 140 | /** |
| @@ -135,8 +155,12 @@ | ||
| 135 | 155 | 'current_server' => array( |
| 136 | 156 | 'type' => 'string', |
| 137 | 157 | 'sanitize' => 'sanitize_text_field', |
| 138 | 158 | ), |
| 159 | + 'username' => array( | |
| 160 | + 'type' => 'string', | |
| 161 | + 'sanitize' => 'sanitize_user', | |
| 162 | + ), | |
| 139 | 163 | ) |
| 140 | 164 | ); |
| 141 | 165 | |
| 142 | 166 | $slug = $data['slug'] ?? false; |
| @@ -148,8 +172,39 @@ | ||
| 148 | 172 | array( 'message' => esc_html__( 'Invalid request', 'defender-security' ) ) |
| 149 | 173 | ); |
| 150 | 174 | } |
| 151 | 175 | |
| 176 | + if ( 'prevent-enum-users' === $slug ) { | |
| 177 | + $pe_data = $request->get_data(); | |
| 178 | + $enabled_enums = $pe_data['enabled_user_enums'] ?? null; | |
| 179 | + $all_enums = array( 'enum-rest', 'enum-oembed', 'enum-sitemap' ); | |
| 180 | + $this->prevent_enum_users->set_enabled_user_enums( | |
| 181 | + is_array( $enabled_enums ) ? $enabled_enums : $all_enums | |
| 182 | + ); | |
| 183 | + } | |
| 184 | + | |
| 185 | + if ( 'security-key' === $slug ) { | |
| 186 | + $sk_data = $request->get_data(); | |
| 187 | + $remind_date = sanitize_text_field( $sk_data['remind_date'] ?? '' ); | |
| 188 | + if ( $remind_date ) { | |
| 189 | + $is_autogen = filter_var( $sk_data['is_autogenerate_keys'] ?? false, FILTER_VALIDATE_BOOLEAN ); | |
| 190 | + $values = get_site_option( 'defender_security_tweaks_' . $this->security_key->slug, array() ); | |
| 191 | + $values['reminder_duration'] = $remind_date; | |
| 192 | + $values['reminder_date'] = strtotime( '+' . $remind_date, time() ); | |
| 193 | + $values['is_autogenerate_keys'] = $is_autogen; | |
| 194 | + update_site_option( 'defender_security_tweaks_' . $this->security_key->slug, $values ); | |
| 195 | + if ( true === $is_autogen ) { | |
| 196 | + $this->security_key->cron_schedule(); | |
| 197 | + } else { | |
| 198 | + $this->security_key->cron_unschedule(); | |
| 199 | + } | |
| 200 | + } | |
| 201 | + } | |
| 202 | + | |
| 203 | + if ( 'replace-admin-username' === $slug && isset( $data['username'] ) && '' !== trim( $data['username'] ) ) { | |
| 204 | + $_POST['username'] = $data['username']; | |
| 205 | + } | |
| 206 | + | |
| 152 | 207 | if ( in_array( $slug, array( 'prevent-php-executed', 'protect-information' ), true ) ) { |
| 153 | 208 | $current_server = $data['current_server'] ?? false; |
| 154 | 209 | if ( ! $current_server ) { |
| 155 | 210 | return new Response( |
| @@ -170,8 +225,14 @@ | ||
| 170 | 225 | $this->track_tweak( $tweak->get_label(), 'Actioned' ); |
| 171 | 226 | // Response. |
| 172 | 227 | $this->ajax_response( esc_html__( 'Security recommendation successfully resolved.', 'defender-security' ) ); |
| 173 | 228 | } |
| 229 | + if ( $ret instanceof Response && true === $ret->status ) { | |
| 230 | + Config_Hub_Helper::set_clear_active_flag(); | |
| 231 | + $this->model->mark( self::STATUS_RESOLVE, $slug ); | |
| 232 | + $this->track_tweak( $tweak->get_label(), 'Actioned' ); | |
| 233 | + return $ret; | |
| 234 | + } | |
| 174 | 235 | if ( is_wp_error( $ret ) ) { |
| 175 | 236 | $this->ajax_response( $ret->get_error_message(), false ); |
| 176 | 237 | } |
| 177 | 238 | |
| @@ -269,11 +330,12 @@ | ||
| 269 | 330 | } |
| 270 | 331 | $this->model->mark( self::STATUS_IGNORE, $slug ); |
| 271 | 332 | // Track. |
| 272 | 333 | $this->track_tweak( $tweak->get_label(), 'Ignored' ); |
| 334 | + if ( 'security-key' === $slug ) { | |
| 335 | + $this->security_key->cron_unschedule(); | |
| 336 | + } | |
| 273 | 337 | |
| 274 | - $this->security_key->cron_unschedule(); | |
| 275 | - | |
| 276 | 338 | $this->ajax_response( esc_html__( 'Security recommendation successfully ignored.', 'defender-security' ) ); |
| 277 | 339 | } |
| 278 | 340 | |
| 279 | 341 | /** |
| @@ -305,10 +367,8 @@ | ||
| 305 | 367 | // Track. |
| 306 | 368 | $this->track_tweak( $tweak->get_label(), 'Restored' ); |
| 307 | 369 | |
| 308 | 370 | if ( $this->security_key->get_is_autogenerate_keys() ) { |
| 309 | - // Mandatory: cron_schedule method bypass scheduling if already a schedule for this job. | |
| 310 | - $this->security_key->cron_unschedule(); | |
| 311 | 371 | $this->security_key->cron_schedule(); |
| 312 | 372 | } |
| 313 | 373 | |
| 314 | 374 | $this->ajax_response( esc_html__( 'Security recommendation successfully restored.', 'defender-security' ) ); |
| @@ -402,11 +462,11 @@ | ||
| 402 | 462 | |
| 403 | 463 | if ( update_site_option( 'defender_security_tweaks_' . $this->security_key->slug, $values ) ) { |
| 404 | 464 | |
| 405 | 465 | if ( true === $is_autogen_flag ) { |
| 406 | - // Mandatory: cron_schedule method bypass scheduling if already a schedule for this job. | |
| 466 | + $this->security_key->cron_schedule(); | |
| 467 | + } else { | |
| 407 | 468 | $this->security_key->cron_unschedule(); |
| 408 | - $this->security_key->cron_schedule(); | |
| 409 | 469 | } |
| 410 | 470 | |
| 411 | 471 | return new Response( |
| 412 | 472 | true, |
| @@ -420,8 +480,37 @@ | ||
| 420 | 480 | } |
| 421 | 481 | } |
| 422 | 482 | |
| 423 | 483 | /** |
| 484 | + * Returns tweaks for a given status formatted for the React frontend. | |
| 485 | + * | |
| 486 | + * Adds `category` and remaps the backend status string to the value the | |
| 487 | + * frontend HARDENING_STATUS constants expect ('issue', 'resolved', 'ignored'). | |
| 488 | + * | |
| 489 | + * @param string $status One of STATUS_ISSUES, STATUS_RESOLVE, STATUS_IGNORE. | |
| 490 | + * | |
| 491 | + * @return array | |
| 492 | + */ | |
| 493 | + private function format_tweaks_for_frontend( string $status ): array { | |
| 494 | + $status_map = array( | |
| 495 | + self::STATUS_ISSUES => 'issue', | |
| 496 | + self::STATUS_RESOLVE => 'resolved', | |
| 497 | + self::STATUS_IGNORE => 'ignored', | |
| 498 | + ); | |
| 499 | + | |
| 500 | + $tweaks = $this->init_tweaks( $status, 'array' ); | |
| 501 | + $frontend_status = $status_map[ $status ] ?? $status; | |
| 502 | + | |
| 503 | + foreach ( $tweaks as &$tweak ) { | |
| 504 | + $tweak['status'] = $frontend_status; | |
| 505 | + $tweak['category'] = self::TWEAK_CATEGORIES[ $tweak['slug'] ] ?? 'access'; | |
| 506 | + } | |
| 507 | + unset( $tweak ); | |
| 508 | + | |
| 509 | + return $tweaks; | |
| 510 | + } | |
| 511 | + | |
| 512 | + /** | |
| 424 | 513 | * AJAX Response handler. |
| 425 | 514 | * |
| 426 | 515 | * @param string $message The message to be displayed in the response. |
| 427 | 516 | * @param bool $is_success Whether the response should have a success status. |
| @@ -431,22 +520,23 @@ | ||
| 431 | 520 | */ |
| 432 | 521 | private function ajax_response( $message, $is_success = true, $interval = false ): Response { |
| 433 | 522 | global $wp_version; |
| 434 | 523 | |
| 435 | - $settings = new Model_Security_Tweaks(); | |
| 436 | - $data = array( | |
| 524 | + // Creating a new Model_Security_Tweaks() here risks reading stale data from the | |
| 525 | + // DB when the WP options cache hasn't flushed the write yet. | |
| 526 | + $data = array( | |
| 437 | 527 | 'message' => $message, |
| 438 | 528 | 'summary' => array( |
| 439 | - 'issues_count' => count( $settings->issues ), | |
| 440 | - 'fixed_count' => count( $settings->fixed ), | |
| 441 | - 'ignore_count' => count( $settings->ignore ), | |
| 529 | + 'issues_count' => count( $this->model->issues ), | |
| 530 | + 'fixed_count' => count( $this->model->fixed ), | |
| 531 | + 'ignore_count' => count( $this->model->ignore ), | |
| 442 | 532 | 'php_version' => PHP_VERSION, |
| 443 | 533 | 'wp_version' => $wp_version, |
| 444 | 534 | ), |
| 445 | - 'issues' => $this->init_tweaks( self::STATUS_ISSUES, 'array' ), | |
| 446 | - 'fixed' => $this->init_tweaks( self::STATUS_RESOLVE, 'array' ), | |
| 447 | - 'ignored' => $this->init_tweaks( self::STATUS_IGNORE, 'array' ), | |
| 448 | - 'issues_slugs' => $settings->issues, | |
| 535 | + 'issues' => $this->build_tweaks_response( $this->model->issues, 'issue' ), | |
| 536 | + 'fixed' => $this->build_tweaks_response( $this->model->fixed, 'resolved' ), | |
| 537 | + 'ignored' => $this->build_tweaks_response( $this->model->ignore, 'ignored' ), | |
| 538 | + 'issues_slugs' => $this->model->issues, | |
| 449 | 539 | ); |
| 450 | 540 | if ( $interval ) { |
| 451 | 541 | $data['interval'] = $interval; |
| 452 | 542 | } |
| @@ -454,8 +544,36 @@ | ||
| 454 | 544 | return new Response( $is_success, $data ); |
| 455 | 545 | } |
| 456 | 546 | |
| 457 | 547 | /** |
| 548 | + * Builds the frontend-shaped tweak array from a list of slugs. | |
| 549 | + * | |
| 550 | + * Uses the in-memory tweak object cache rather than re-querying the model, | |
| 551 | + * so the result is always consistent with the state after mark() was called. | |
| 552 | + * | |
| 553 | + * @param array $slugs Slugs to include. | |
| 554 | + * @param string $frontend_status Frontend status string ('issue', 'resolved', 'ignored'). | |
| 555 | + * | |
| 556 | + * @return array | |
| 557 | + */ | |
| 558 | + private function build_tweaks_response( array $slugs, string $frontend_status ): array { | |
| 559 | + $tweaks = Array_Cache::get( 'tweaks', 'tweaks' ); | |
| 560 | + $result = array(); | |
| 561 | + | |
| 562 | + foreach ( $slugs as $slug ) { | |
| 563 | + if ( ! isset( $tweaks[ $slug ] ) ) { | |
| 564 | + continue; | |
| 565 | + } | |
| 566 | + $arr = $tweaks[ $slug ]->to_array(); | |
| 567 | + $arr['status'] = $frontend_status; | |
| 568 | + $arr['category'] = self::TWEAK_CATEGORIES[ $slug ] ?? 'access'; | |
| 569 | + $result[ $slug ] = $arr; | |
| 570 | + } | |
| 571 | + | |
| 572 | + return $result; | |
| 573 | + } | |
| 574 | + | |
| 575 | + /** | |
| 458 | 576 | * Enqueues scripts and styles for this page. |
| 459 | 577 | * Only enqueues assets if the page is active. |
| 460 | 578 | */ |
| 461 | 579 | public function enqueue_assets() { |
| @@ -462,10 +580,34 @@ | ||
| 462 | 580 | if ( ! $this->is_page_active() ) { |
| 463 | 581 | return; |
| 464 | 582 | } |
| 465 | 583 | |
| 466 | - wp_localize_script( 'def-securitytweaks', 'security_tweaks', $this->data_frontend() ); | |
| 467 | - wp_enqueue_script( 'def-securitytweaks' ); | |
| 584 | + $handle = 'defender-ui-hardening'; | |
| 585 | + wp_enqueue_script( | |
| 586 | + $handle, | |
| 587 | + WP_DEFENDER_BASE_URL . 'assets/js/hardening-ui.js', | |
| 588 | + array( 'def-vue', 'def-manifest', 'def-core-ui', 'defender', 'wp-i18n' ), | |
| 589 | + DEFENDER_VERSION, | |
| 590 | + true | |
| 591 | + ); | |
| 592 | + wp_set_script_translations( $handle, 'wpdef' ); | |
| 593 | + | |
| 594 | + wp_localize_script( | |
| 595 | + $handle, | |
| 596 | + 'defenderUIData', | |
| 597 | + array_merge( | |
| 598 | + $this->get_shared_data(), | |
| 599 | + $this->data_frontend() | |
| 600 | + ) | |
| 601 | + ); | |
| 602 | + | |
| 603 | + wp_enqueue_style( | |
| 604 | + $handle, | |
| 605 | + WP_DEFENDER_BASE_URL . 'assets/css/core-ui.css', | |
| 606 | + array(), | |
| 607 | + DEFENDER_VERSION | |
| 608 | + ); | |
| 609 | + | |
| 468 | 610 | $this->enqueue_main_assets(); |
| 469 | 611 | } |
| 470 | 612 | |
| 471 | 613 | /** |
| @@ -510,34 +652,11 @@ | ||
| 510 | 652 | $not_allowed_bulk[] = 'protect-information'; |
| 511 | 653 | $not_allowed_bulk[] = 'prevent-php-executed'; |
| 512 | 654 | } |
| 513 | 655 | |
| 514 | - $tweak_arr = $this->model->get_tweak_types(); | |
| 515 | - $total_tweaks = $tweak_arr['count_fixed'] + $tweak_arr['count_ignored'] + $tweak_arr['count_issues']; | |
| 656 | + $tweak_arr = $this->model->get_tweak_types(); | |
| 516 | 657 | |
| 517 | - // Prepare additional data. | |
| 518 | - if ( defender_is_wp_org_version() ) { | |
| 519 | - $misc = array( | |
| 520 | - 'rating_is_displayed' => ! Rate::was_rate_request() && $tweak_arr['count_fixed'] === $total_tweaks, | |
| 521 | - 'rating_text' => sprintf( | |
| 522 | - /* translators: %d - Total number. */ | |
| 523 | - esc_html__( | |
| 524 | - 'You`ve resolved all %d security recommendations - that`s impressive! We are happy to be a part of helping you secure your site, and we would appreciate it if you dropped us a rating on wp.org to help us spread the word and boost our motivation.', | |
| 525 | - 'defender-security' | |
| 526 | - ), | |
| 527 | - $total_tweaks | |
| 528 | - ), | |
| 529 | - 'rating_type' => 'tweak', | |
| 530 | - ); | |
| 531 | - } else { | |
| 532 | - $misc = array( | |
| 533 | - 'rating_is_displayed' => false, | |
| 534 | - 'rating_text' => '', | |
| 535 | - 'rating_type' => '', | |
| 536 | - ); | |
| 537 | - } | |
| 538 | - | |
| 539 | - $data = array( | |
| 658 | + $hardening_data = array( | |
| 540 | 659 | 'summary' => array( |
| 541 | 660 | 'fixed_count' => $tweak_arr['count_fixed'], |
| 542 | 661 | 'ignore_count' => $tweak_arr['count_ignored'], |
| 543 | 662 | 'issues_count' => $tweak_arr['count_issues'], |
| @@ -543,20 +662,20 @@ | ||
| 543 | 662 | 'issues_count' => $tweak_arr['count_issues'], |
| 544 | 663 | 'php_version' => PHP_VERSION, |
| 545 | 664 | 'wp_version' => $wp_version, |
| 546 | 665 | ), |
| 547 | - // Todo: improve the logic so that Tweak initialization happens in one go and return 3 different types. | |
| 548 | - 'issues' => $this->init_tweaks( self::STATUS_ISSUES, 'array' ), | |
| 549 | - 'fixed' => $this->init_tweaks( self::STATUS_RESOLVE, 'array' ), | |
| 550 | - 'ignored' => $this->init_tweaks( self::STATUS_IGNORE, 'array' ), | |
| 666 | + 'issues' => $this->format_tweaks_for_frontend( self::STATUS_ISSUES ), | |
| 667 | + 'fixed' => $this->format_tweaks_for_frontend( self::STATUS_RESOLVE ), | |
| 668 | + 'ignored' => $this->format_tweaks_for_frontend( self::STATUS_IGNORE ), | |
| 551 | 669 | 'not_allowed_bulk' => $not_allowed_bulk, |
| 552 | 670 | 'is_autogenerate_keys' => $this->security_key->get_is_autogenerate_keys(), |
| 553 | 671 | 'reminder_frequencies' => $this->security_key->reminder_frequencies(), |
| 554 | 672 | 'enabled_user_enums' => $this->prevent_enum_users->get_enabled_user_enums(), |
| 555 | - 'misc' => $misc, | |
| 556 | 673 | ); |
| 557 | 674 | |
| 558 | - return array_merge( $data, $this->dump_routes_and_nonces() ); | |
| 675 | + return array( | |
| 676 | + 'hardening' => array_merge( $hardening_data, $this->dump_routes_and_nonces() ), | |
| 677 | + ); | |
| 559 | 678 | } |
| 560 | 679 | |
| 561 | 680 | /** |
| 562 | 681 | * Renders the main view for this page. |
| @@ -589,10 +708,14 @@ | ||
| 589 | 708 | ); |
| 590 | 709 | $slugs = $data['slugs'] ?? array(); |
| 591 | 710 | $intention = $data['intention'] ?? false; |
| 592 | 711 | // Get processed and unprocessed tweaks. |
| 593 | - [ $processed, $unprocessed ] = $this->security_tweaks_auto_action( $slugs, $intention ); | |
| 712 | + [ $processed, $unprocessed, $error_message ] = $this->security_tweaks_auto_action( $slugs, $intention ); | |
| 594 | 713 | |
| 714 | + if ( null !== $error_message ) { | |
| 715 | + return $this->ajax_response( $error_message, false ); | |
| 716 | + } | |
| 717 | + | |
| 595 | 718 | $message = sprintf( |
| 596 | 719 | /* translators: 1: Either ignored or resolved, 2: Count security recommendations */ |
| 597 | 720 | esc_html__( 'You have bulk %1$s %2$s security recommendations.', 'defender-security' ), |
| 598 | 721 | 'ignore' === $intention ? 'ignored' : 'resolved', |
| @@ -624,10 +747,11 @@ | ||
| 624 | 747 | * |
| 625 | 748 | * @return array |
| 626 | 749 | */ |
| 627 | 750 | public function security_tweaks_auto_action( $slugs, $intention ): array { |
| 628 | - $processed = 0; | |
| 629 | - $unprocessed = 0; | |
| 751 | + $processed = 0; | |
| 752 | + $unprocessed = 0; | |
| 753 | + $error_message = null; | |
| 630 | 754 | |
| 631 | 755 | foreach ( $slugs as $slug ) { |
| 632 | 756 | $tweak = $this->get_tweak( $slug ); |
| 633 | 757 | if ( 'ignore' === $intention ) { |
| @@ -651,29 +775,34 @@ | ||
| 651 | 775 | } else { |
| 652 | 776 | $ret = $tweak->process(); |
| 653 | 777 | } |
| 654 | 778 | if ( is_wp_error( $ret ) ) { |
| 655 | - $this->ajax_response( | |
| 656 | - sprintf( | |
| 657 | - /* translators: 1: Security tweak title, 2: Error message */ | |
| 658 | - esc_html__( | |
| 659 | - 'There is an error while processing recommendation %1$s, error message: %2$s', | |
| 660 | - 'defender-security' | |
| 661 | - ), | |
| 662 | - $tweak->get_label(), | |
| 663 | - $ret->get_error_message() | |
| 779 | + $error_message = sprintf( | |
| 780 | + /* translators: 1: Security tweak title, 2: Error message */ | |
| 781 | + esc_html__( | |
| 782 | + 'There is an error while processing recommendation %1$s, error message: %2$s', | |
| 783 | + 'defender-security' | |
| 664 | 784 | ), |
| 665 | - false | |
| 785 | + $tweak->get_label(), | |
| 786 | + $ret->get_error_message() | |
| 666 | 787 | ); |
| 788 | + ++$unprocessed; | |
| 789 | + // Don't mark a failed tweak as resolved; stop on first error (same as original behavior). | |
| 790 | + break; | |
| 667 | 791 | } |
| 668 | 792 | $this->model->mark( self::STATUS_RESOLVE, $slug ); |
| 669 | 793 | // Track. |
| 670 | 794 | $this->track_tweak( $tweak->get_label(), 'Actioned' ); |
| 795 | + } elseif ( 'restore' === $intention ) { | |
| 796 | + // New bulk action since V6. | |
| 797 | + $this->model->mark( self::STATUS_ISSUES, $slug ); | |
| 798 | + // Track. | |
| 799 | + $this->track_tweak( $tweak->get_label(), 'Restored' ); | |
| 671 | 800 | } |
| 672 | 801 | ++$processed; |
| 673 | 802 | } |
| 674 | 803 | |
| 675 | - return array( $processed, $unprocessed ); | |
| 804 | + return array( $processed, $unprocessed, $error_message ); | |
| 676 | 805 | } |
| 677 | 806 | |
| 678 | 807 | /** |
| 679 | 808 | * Refresh the tweak status and save their state. |
| @@ -757,13 +886,19 @@ | ||
| 757 | 886 | } |
| 758 | 887 | Array_Cache::set( 'tweaks', $tweaks, 'tweaks' ); |
| 759 | 888 | } |
| 760 | 889 | $tmp = array(); |
| 761 | - if ( empty( $type ) ) { | |
| 890 | + if ( ! is_string( $type ) || '' === trim( $type ) ) { | |
| 762 | 891 | $tmp = $tweaks; |
| 763 | 892 | } else { |
| 764 | 893 | $settings = new Model_Security_Tweaks(); |
| 765 | - $compare = $settings->$type; | |
| 894 | + | |
| 895 | + try { | |
| 896 | + $compare = $settings->get_collection( $type ); | |
| 897 | + } catch ( \InvalidArgumentException $e ) { | |
| 898 | + $compare = array(); | |
| 899 | + } | |
| 900 | + | |
| 766 | 901 | foreach ( $compare as $slug ) { |
| 767 | 902 | if ( isset( $tweaks[ $slug ] ) ) { |
| 768 | 903 | $tmp[ $slug ] = $tweaks[ $slug ]; |
| 769 | 904 | } |
| @@ -832,9 +967,9 @@ | ||
| 832 | 967 | ( new Model_Security_Tweaks() )->delete(); |
| 833 | 968 | |
| 834 | 969 | delete_site_transient( Server::CACHE_CURRENT_SERVER ); |
| 835 | 970 | delete_site_transient( \WP_Defender\Component\Security_Tweaks\Servers\Apache::CACHE_APACHE_VERSION ); |
| 836 | - wp_clear_scheduled_hook( 'wpdef_sec_key_gen' ); | |
| 971 | + $this->security_key->cron_unschedule(); | |
| 837 | 972 | } |
| 838 | 973 | |
| 839 | 974 | /** |
| 840 | 975 | * Delete all the data & the cache. |
| @@ -857,9 +992,9 @@ | ||
| 857 | 992 | public function automate( $data, $request_reason ) { |
| 858 | 993 | $this->refresh_tweaks_status(); |
| 859 | 994 | $need_reauth = false; |
| 860 | 995 | // Resolve tweaks. |
| 861 | - if ( ! empty( $data['fixed'] ) ) { | |
| 996 | + if ( isset( $data['fixed'] ) && is_array( $data['fixed'] ) && array() !== $data['fixed'] ) { | |
| 862 | 997 | // There are some tweak that need manual apply, as files based, or change admin. |
| 863 | 998 | $manual_done = array( |
| 864 | 999 | 'replace-admin-username', |
| 865 | 1000 | 'prevent-php-executed', |
| @@ -871,9 +1006,9 @@ | ||
| 871 | 1006 | $manual_done[] = 'security-key'; |
| 872 | 1007 | } |
| 873 | 1008 | |
| 874 | 1009 | $diff_keys = array_diff( $data['fixed'], $this->model->fixed, $manual_done ); |
| 875 | - if ( ! empty( $diff_keys ) ) { | |
| 1010 | + if ( array() !== $diff_keys ) { | |
| 876 | 1011 | foreach ( $diff_keys as $slug ) { |
| 877 | 1012 | $tweak = $this->get_tweak( $slug ); |
| 878 | 1013 | if ( $tweak->has_method( 'bulk_process' ) ) { |
| 879 | 1014 | $ret = $tweak->bulk_process(); |
| @@ -904,12 +1039,12 @@ | ||
| 904 | 1039 | } |
| 905 | 1040 | } |
| 906 | 1041 | } |
| 907 | 1042 | // Revert tweaks. |
| 908 | - if ( ! empty( $data['issues'] ) ) { | |
| 1043 | + if ( isset( $data['issues'] ) && is_array( $data['issues'] ) && array() !== $data['issues'] ) { | |
| 909 | 1044 | $diff_keys = array_diff( $data['issues'], $this->model->issues ); |
| 910 | 1045 | |
| 911 | - if ( ! empty( $diff_keys ) ) { | |
| 1046 | + if ( array() !== $diff_keys ) { | |
| 912 | 1047 | // Issues. |
| 913 | 1048 | foreach ( $diff_keys as $slug ) { |
| 914 | 1049 | $tweak = $this->get_tweak( $slug ); |
| 915 | 1050 | $ret = $tweak->revert(); |
| @@ -932,11 +1067,11 @@ | ||
| 932 | 1067 | } |
| 933 | 1068 | } |
| 934 | 1069 | } |
| 935 | 1070 | // Ignore tweaks. |
| 936 | - if ( ! empty( $data['ignore'] ) ) { | |
| 1071 | + if ( isset( $data['ignore'] ) && is_array( $data['ignore'] ) && array() !== $data['ignore'] ) { | |
| 937 | 1072 | $diff_keys = array_diff( $data['ignore'], $this->model->ignore ); |
| 938 | - if ( ! empty( $diff_keys ) ) { | |
| 1073 | + if ( array() !== $diff_keys ) { | |
| 939 | 1074 | foreach ( $diff_keys as $slug ) { |
| 940 | 1075 | $this->model->mark( self::STATUS_IGNORE, $slug ); |
| 941 | 1076 | } |
| 942 | 1077 | } |
| @@ -960,9 +1095,9 @@ | ||
| 960 | 1095 | } |
| 961 | 1096 | |
| 962 | 1097 | $this->prevent_enum_users->set_enabled_user_enums( $enabled_user_enums ); |
| 963 | 1098 | |
| 964 | - if ( ! empty( $data['security_key'] ) && is_array( $data['security_key'] ) ) { | |
| 1099 | + if ( isset( $data['security_key'] ) && is_array( $data['security_key'] ) && array() !== $data['security_key'] ) { | |
| 965 | 1100 | $this->security_key->update_all_option( $data['security_key'] ); |
| 966 | 1101 | } |
| 967 | 1102 | |
| 968 | 1103 | $model = new Model_Security_Tweaks(); |
| @@ -983,9 +1118,9 @@ | ||
| 983 | 1118 | $settings = new Model_Security_Tweaks(); |
| 984 | 1119 | $strings = array(); |
| 985 | 1120 | $count_all = count( $settings->fixed ) + count( $settings->issues ) + count( $settings->ignore ); |
| 986 | 1121 | |
| 987 | - if ( empty( $settings->issues ) ) { | |
| 1122 | + if ( ! is_array( $settings->issues ) || array() === $settings->issues ) { | |
| 988 | 1123 | $strings[] = esc_html__( 'All available recommendations activated', 'defender-security' ); |
| 989 | 1124 | } else { |
| 990 | 1125 | $strings[] = sprintf( |
| 991 | 1126 | /* translators: 1: Total security tweaks activated, 2: Total security tweaks */ |
| @@ -994,13 +1129,8 @@ | ||
| 994 | 1129 | $count_all |
| 995 | 1130 | ); |
| 996 | 1131 | } |
| 997 | 1132 | |
| 998 | - $tweak_notification = new Tweak_Reminder(); | |
| 999 | - if ( 'enabled' === $tweak_notification->status ) { | |
| 1000 | - $strings[] = esc_html__( 'Email notifications active', 'defender-security' ); | |
| 1001 | - } | |
| 1002 | - | |
| 1003 | 1133 | return $strings; |
| 1004 | 1134 | } |
| 1005 | 1135 | |
| 1006 | 1136 | /** |
| @@ -1006,15 +1136,18 @@ | ||
| 1006 | 1136 | /** |
| 1007 | 1137 | * Config strings. |
| 1008 | 1138 | * |
| 1009 | 1139 | * @param array $config Settings. |
| 1010 | - * @param bool $is_pro True if it is a pro version. | |
| 1011 | 1140 | * |
| 1012 | 1141 | * @return array Strings to be displayed in the settings page. |
| 1013 | 1142 | */ |
| 1014 | - public function config_strings( $config, $is_pro ): array { | |
| 1143 | + public function config_strings( $config ): array { | |
| 1015 | 1144 | $strings = array(); |
| 1016 | - if ( empty( $config['issues'] ) ) { | |
| 1145 | + if ( | |
| 1146 | + ! isset( $config['issues'] ) | |
| 1147 | + || ! ( is_array( $config['issues'] ) || $config['issues'] instanceof Countable ) | |
| 1148 | + || 0 === count( $config['issues'] ) | |
| 1149 | + ) { | |
| 1017 | 1150 | $strings[] = esc_html__( 'All available recommendations activated', 'defender-security' ); |
| 1018 | 1151 | } else { |
| 1019 | 1152 | $strings[] = sprintf( |
| 1020 | 1153 | /* translators: 1: Total security tweaks activated, 2: Total security tweaks */ |
| @@ -1084,9 +1217,10 @@ | ||
| 1084 | 1217 | * @return Response |
| 1085 | 1218 | * @defender_route |
| 1086 | 1219 | */ |
| 1087 | 1220 | public function update_enabled_user_enums( Request $request ): Response { |
| 1088 | - $data = (array) $request->get_data(); | |
| 1221 | + $data = $request->get_data(); | |
| 1222 | + $data = ! is_array( $data ) ? (array) $data : $data; | |
| 1089 | 1223 | $enabled_user_enums = $data['enabled_user_enums']; |
| 1090 | 1224 | $is_success = false; |
| 1091 | 1225 | $message = esc_html__( 'An error occurred, try again.', 'defender-security' ); |
| 1092 | 1226 | |
| @@ -1098,36 +1232,8 @@ | ||
| 1098 | 1232 | return new Response( |
| 1099 | 1233 | $is_success, |
| 1100 | 1234 | array( 'message' => $message ) |
| 1101 | 1235 | ); |
| 1102 | - } | |
| 1103 | - | |
| 1104 | - /** | |
| 1105 | - * Handle tweaks rating notice. | |
| 1106 | - * | |
| 1107 | - * @param Request $request Request. | |
| 1108 | - * | |
| 1109 | - * @return Response | |
| 1110 | - * @defender_route | |
| 1111 | - */ | |
| 1112 | - public function handle_notice( Request $request ): Response { | |
| 1113 | - update_site_option( Rate::SLUG_FOR_BUTTON_RATE, true ); | |
| 1114 | - | |
| 1115 | - return new Response( true, array() ); | |
| 1116 | - } | |
| 1117 | - | |
| 1118 | - /** | |
| 1119 | - * Attention: Tweaks rating notice doesn't have postpone_notice route. | |
| 1120 | - * | |
| 1121 | - * @param Request $request Request object. | |
| 1122 | - * | |
| 1123 | - * @defender_route | |
| 1124 | - * @return Response | |
| 1125 | - */ | |
| 1126 | - public function refuse_notice( Request $request ): Response { | |
| 1127 | - update_site_option( Rate::SLUG_FOR_BUTTON_THANKS, true ); | |
| 1128 | - | |
| 1129 | - return new Response( true, array() ); | |
| 1130 | 1236 | } |
| 1131 | 1237 | |
| 1132 | 1238 | /** |
| 1133 | 1239 | * Check XML-RPC status. |