| 1 |
<?php |
| 2 |
/** |
| 3 |
* Security Analyzer — AJAX handlers (trait). |
| 4 |
* |
| 5 |
* Mixed into Vigilante_Admin via `use Vigilante_Admin_Analyzer_Ajax;`. |
| 6 |
* All handlers require `manage_options` and a valid vigilante_admin_nonce. |
| 7 |
* |
| 8 |
* @package Vigilante |
| 9 |
* @since 2.1.0 |
| 10 |
*/ |
| 11 |
|
| 12 |
// Prevent direct access. |
| 13 |
if ( ! defined( 'ABSPATH' ) ) { |
| 14 |
exit; |
| 15 |
} |
| 16 |
|
| 17 |
/** |
| 18 |
* Trait carrying the three analyzer AJAX endpoints. |
| 19 |
*/ |
| 20 |
trait Vigilante_Admin_Analyzer_Ajax { |
| 21 |
|
| 22 |
/** |
| 23 |
* POST vigilante_analyzer_run |
| 24 |
* payload: { phase: 'fast'|'slow'|'all' } |
| 25 |
* returns: the scan report (from Vigilante_Security_Analyzer::run_scan()). |
| 26 |
*/ |
| 27 |
public function ajax_analyzer_run() { |
| 28 |
check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); |
| 29 |
|
| 30 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 31 |
wp_send_json_error( __( 'Permission denied.', 'vigilante' ), 403 ); |
| 32 |
} |
| 33 |
|
| 34 |
$phase = isset( $_POST['phase'] ) ? sanitize_key( wp_unslash( $_POST['phase'] ) ) : 'all'; |
| 35 |
if ( ! in_array( $phase, array( 'fast', 'slow', 'all' ), true ) ) { |
| 36 |
$phase = 'all'; |
| 37 |
} |
| 38 |
|
| 39 |
try { |
| 40 |
$analyzer = $this->get_security_analyzer(); |
| 41 |
if ( ! $analyzer ) { |
| 42 |
wp_send_json_error( __( 'Security Analyzer is not available.', 'vigilante' ) ); |
| 43 |
} |
| 44 |
// Run the phase (persists and merges with prior state). |
| 45 |
$analyzer->run_scan( $phase ); |
| 46 |
// Return the merged state so the UI progressively shows everything seen so far. |
| 47 |
$merged = $analyzer->get_last_scan(); |
| 48 |
if ( empty( $merged ) ) { |
| 49 |
wp_send_json_error( __( 'Scan returned no data.', 'vigilante' ) ); |
| 50 |
} |
| 51 |
$merged['phase'] = $phase; |
| 52 |
$merged['catalog'] = $analyzer->get_catalog(); |
| 53 |
wp_send_json_success( $merged ); |
| 54 |
} catch ( Exception $e ) { |
| 55 |
wp_send_json_error( 'Exception: ' . $e->getMessage() ); |
| 56 |
} catch ( Error $e ) { |
| 57 |
wp_send_json_error( 'Error: ' . $e->getMessage() ); |
| 58 |
} |
| 59 |
} |
| 60 |
|
| 61 |
/** |
| 62 |
* POST vigilante_analyzer_history |
| 63 |
* returns: up to HISTORY_LIMIT entries of { ran_at, score, grade, categories[...] }. |
| 64 |
*/ |
| 65 |
public function ajax_analyzer_history() { |
| 66 |
check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); |
| 67 |
|
| 68 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 69 |
wp_send_json_error( __( 'Permission denied.', 'vigilante' ), 403 ); |
| 70 |
} |
| 71 |
|
| 72 |
$analyzer = $this->get_security_analyzer(); |
| 73 |
if ( ! $analyzer ) { |
| 74 |
wp_send_json_error( __( 'Security Analyzer is not available.', 'vigilante' ) ); |
| 75 |
} |
| 76 |
|
| 77 |
$limit = isset( $_POST['limit'] ) ? max( 1, min( 60, (int) $_POST['limit'] ) ) : 30; |
| 78 |
$history = $analyzer->get_score_history( $limit ); |
| 79 |
|
| 80 |
wp_send_json_success( |
| 81 |
array( |
| 82 |
'history' => array_values( $history ), |
| 83 |
'limit' => $limit, |
| 84 |
) |
| 85 |
); |
| 86 |
} |
| 87 |
|
| 88 |
/** |
| 89 |
* POST vigilante_analyzer_dismiss_notice |
| 90 |
* payload: { key: string } |
| 91 |
* marks a one-time analyzer notice (e.g. "enable weekly email") as dismissed. |
| 92 |
*/ |
| 93 |
public function ajax_analyzer_dismiss_notice() { |
| 94 |
check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); |
| 95 |
|
| 96 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 97 |
wp_send_json_error( __( 'Permission denied.', 'vigilante' ), 403 ); |
| 98 |
} |
| 99 |
|
| 100 |
$key = isset( $_POST['key'] ) ? sanitize_key( wp_unslash( $_POST['key'] ) ) : ''; |
| 101 |
if ( '' === $key ) { |
| 102 |
wp_send_json_error( __( 'Missing key.', 'vigilante' ) ); |
| 103 |
} |
| 104 |
|
| 105 |
$dismissed = get_option( 'vigilante_dismissed_notices', array() ); |
| 106 |
if ( ! is_array( $dismissed ) ) { |
| 107 |
$dismissed = array(); |
| 108 |
} |
| 109 |
$dismissed[ 'analyzer_' . $key ] = time(); |
| 110 |
update_option( 'vigilante_dismissed_notices', $dismissed, false ); |
| 111 |
|
| 112 |
wp_send_json_success(); |
| 113 |
} |
| 114 |
|
| 115 |
/** |
| 116 |
* POST vigilante_analyzer_save_settings |
| 117 |
* payload: { weekly_scan_enabled: 0|1, email_on_regression: 0|1 } |
| 118 |
* Saves the `security_analyzer` subsection of vigilante_options. |
| 119 |
*/ |
| 120 |
public function ajax_analyzer_save_settings() { |
| 121 |
check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); |
| 122 |
|
| 123 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 124 |
wp_send_json_error( __( 'Permission denied.', 'vigilante' ), 403 ); |
| 125 |
} |
| 126 |
|
| 127 |
if ( ! isset( $this->settings ) || ! $this->settings ) { |
| 128 |
wp_send_json_error( __( 'Settings not available.', 'vigilante' ) ); |
| 129 |
} |
| 130 |
|
| 131 |
$weekly_enabled = ! empty( $_POST['weekly_scan_enabled'] ) ? 1 : 0; |
| 132 |
$email_regress = ! empty( $_POST['email_on_regression'] ) ? 1 : 0; |
| 133 |
|
| 134 |
$section = array( |
| 135 |
'weekly_scan_enabled' => (bool) $weekly_enabled, |
| 136 |
'email_on_regression' => (bool) $email_regress, |
| 137 |
); |
| 138 |
|
| 139 |
$this->settings->update_section( 'security_analyzer', $section ); |
| 140 |
|
| 141 |
// Keep the cron schedule in sync with the toggle. |
| 142 |
$hook = 'vigilante_analyzer_weekly_scan'; |
| 143 |
if ( $weekly_enabled ) { |
| 144 |
if ( ! wp_next_scheduled( $hook ) ) { |
| 145 |
wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', $hook ); |
| 146 |
} |
| 147 |
} else { |
| 148 |
$timestamp = wp_next_scheduled( $hook ); |
| 149 |
if ( $timestamp ) { |
| 150 |
wp_unschedule_event( $timestamp, $hook ); |
| 151 |
} |
| 152 |
wp_unschedule_hook( $hook ); |
| 153 |
} |
| 154 |
|
| 155 |
wp_send_json_success( |
| 156 |
array( |
| 157 |
'weekly_scan_enabled' => (bool) $weekly_enabled, |
| 158 |
'email_on_regression' => (bool) $email_regress, |
| 159 |
) |
| 160 |
); |
| 161 |
} |
| 162 |
|
| 163 |
/** |
| 164 |
* Lazy-load the Vigilante_Security_Analyzer instance. |
| 165 |
* |
| 166 |
* @return Vigilante_Security_Analyzer|null |
| 167 |
*/ |
| 168 |
protected function get_security_analyzer() { |
| 169 |
if ( ! isset( $this->settings ) || ! $this->settings ) { |
| 170 |
return null; |
| 171 |
} |
| 172 |
if ( ! class_exists( 'Vigilante_Security_Analyzer' ) ) { |
| 173 |
require_once VIGILANTE_INCLUDES_DIR . 'class-security-analyzer.php'; |
| 174 |
} |
| 175 |
$activity_log = isset( $this->activity_log ) ? $this->activity_log : null; |
| 176 |
return new Vigilante_Security_Analyzer( $this->settings, $activity_log ); |
| 177 |
} |
| 178 |
} |
| 179 |
|