This commit is contained in:
Javier Casares 2026-08-23 07:08:42 +00:00
commit e6cba7ef6a
21 changed files with 2559 additions and 1286 deletions

View file

@ -23,10 +23,19 @@ $wpvulnerability_analyze = get_site_option( 'wpvulnerability-analyze' );
* Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages.
*
* @since 2.0.0
* @since 5.1.4 Assets load only on the plugin pages and the dashboard.
*
* @param string $hook The current admin page hook.
*
* @return void
*/
function wpvulnerability_admin_enqueue_scripts() {
function wpvulnerability_admin_enqueue_scripts( $hook ) {
// Load on the network settings page, the dashboard (widget) and the list
// tables where vulnerability columns are rendered.
if ( ! in_array( $hook, array( 'index.php', 'settings_page_wpvulnerability-options', 'plugins.php', 'themes.php' ), true ) ) {
return;
}
// Enqueue the admin stylesheet.
wp_enqueue_style(
'wpvulnerability-admin',
@ -53,8 +62,6 @@ function wpvulnerability_admin_enqueue_scripts() {
);
}
add_action( 'admin_enqueue_scripts', 'wpvulnerability_admin_enqueue_scripts' );
/**
* Processes the form submission for the WPVulnerability plugin settings in a multisite network admin context.
*
@ -106,92 +113,11 @@ function wpvulnerability_process_network_config_forms() {
if ( isset( $_POST['wpvulnerability-analyze'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$wpvulnerability_sanitized_values = array(
'core' => 0,
'plugins' => 0,
'themes' => 0,
'php' => 0,
'apache' => 0,
'nginx' => 0,
'mariadb' => 0,
'mysql' => 0,
'imagemagick' => 0,
'curl' => 0,
'memcached' => 0,
'redis' => 0,
'sqlite' => 0,
);
$wpvulnerability_analyze_raw = (array) wp_unslash( $_POST['wpvulnerability-analyze'] );
$wpvulnerability_values = array();
foreach ( $wpvulnerability_analyze_raw as $v ) {
$wpvulnerability_values[] = sanitize_text_field( is_scalar( $v ) ? (string) $v : '' );
}
foreach ( $wpvulnerability_values as $data ) {
switch ( $data ) {
case 'core':
$wpvulnerability_sanitized_values['core'] = 1;
break;
case 'plugins':
$wpvulnerability_sanitized_values['plugins'] = 1;
break;
case 'themes':
$wpvulnerability_sanitized_values['themes'] = 1;
break;
case 'php':
$wpvulnerability_sanitized_values['php'] = 1;
break;
case 'apache':
$wpvulnerability_sanitized_values['apache'] = 1;
break;
case 'nginx':
$wpvulnerability_sanitized_values['nginx'] = 1;
break;
case 'mariadb':
$wpvulnerability_sanitized_values['mariadb'] = 1;
break;
case 'mysql':
$wpvulnerability_sanitized_values['mysql'] = 1;
break;
case 'imagemagick':
$wpvulnerability_sanitized_values['imagemagick'] = 1;
break;
case 'curl':
$wpvulnerability_sanitized_values['curl'] = 1;
break;
case 'memcached':
$wpvulnerability_sanitized_values['memcached'] = 1;
break;
case 'redis':
$wpvulnerability_sanitized_values['redis'] = 1;
break;
case 'sqlite':
$wpvulnerability_sanitized_values['sqlite'] = 1;
break;
}
}
update_site_option(
'wpvulnerability-analyze',
array(
'core' => $wpvulnerability_sanitized_values['core'],
'plugins' => $wpvulnerability_sanitized_values['plugins'],
'themes' => $wpvulnerability_sanitized_values['themes'],
'php' => $wpvulnerability_sanitized_values['php'],
'apache' => $wpvulnerability_sanitized_values['apache'],
'nginx' => $wpvulnerability_sanitized_values['nginx'],
'mariadb' => $wpvulnerability_sanitized_values['mariadb'],
'mysql' => $wpvulnerability_sanitized_values['mysql'],
'imagemagick' => $wpvulnerability_sanitized_values['imagemagick'],
'curl' => $wpvulnerability_sanitized_values['curl'],
'memcached' => $wpvulnerability_sanitized_values['memcached'],
'redis' => $wpvulnerability_sanitized_values['redis'],
'sqlite' => $wpvulnerability_sanitized_values['sqlite'],
)
);
unset( $wpvulnerability_sanitized_values );
// Reuse the strict sanitizer so WPVULNERABILITY_HIDE_* constants
// stay enforced on the network settings save path.
update_site_option( 'wpvulnerability-analyze', wpvulnerability_sanitize_analyze( $wpvulnerability_analyze_raw ) );
add_settings_error(
'wpvulnerability-messages',
@ -805,6 +731,7 @@ function wpvulnerability_render_network_admin_tab_notifications() {
</div>
<div class="wpvulnerability-checkbox-group">
<label>
<input type="hidden" name="wpvulnerability-config[notify][email]" value="n" />
<input type="checkbox" name="wpvulnerability-config[notify][email]" value="y" <?php checked( $email_enabled ); ?> onchange="wpvToggleChannelInput('email')" />
<?php esc_html_e( 'Email', 'wpvulnerability' ); ?>
</label>
@ -815,12 +742,13 @@ function wpvulnerability_render_network_admin_tab_notifications() {
</div>
<label>
<input type="hidden" name="wpvulnerability-config[notify][slack]" value="n" />
<input type="checkbox" name="wpvulnerability-config[notify][slack]" value="y" <?php checked( $slack_enabled ); ?> onchange="wpvToggleChannelInput('slack')" />
<?php esc_html_e( 'Slack', 'wpvulnerability' ); ?>
</label>
<div class="wpvulnerability-channel-inputs" id="wpvulnerability-slack-inputs">
<label for="wpvulnerability_slack_webhook"><?php esc_html_e( 'Slack Webhook URL:', 'wpvulnerability' ); ?></label>
<input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="https://hooks.slack.com/services/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['slack_webhook'] ); ?>" />
<input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="https://hooks.slack.com/services/..." value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $wpvulnerability_settings['slack_webhook'] ) ? (string) $wpvulnerability_settings['slack_webhook'] : '' ) ); ?>" />
<span class="wpvulnerability-input-hint">
<?php
printf(
@ -833,12 +761,13 @@ function wpvulnerability_render_network_admin_tab_notifications() {
</div>
<label>
<input type="hidden" name="wpvulnerability-config[notify][teams]" value="n" />
<input type="checkbox" name="wpvulnerability-config[notify][teams]" value="y" <?php checked( $teams_enabled ); ?> onchange="wpvToggleChannelInput('teams')" />
<?php esc_html_e( 'Microsoft Teams', 'wpvulnerability' ); ?>
</label>
<div class="wpvulnerability-channel-inputs" id="wpvulnerability-teams-inputs">
<label for="wpvulnerability_teams_webhook"><?php esc_html_e( 'Teams Webhook URL:', 'wpvulnerability' ); ?></label>
<input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="https://outlook.office.com/webhook/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['teams_webhook'] ); ?>" />
<input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="https://outlook.office.com/webhook/..." value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $wpvulnerability_settings['teams_webhook'] ) ? (string) $wpvulnerability_settings['teams_webhook'] : '' ) ); ?>" />
<span class="wpvulnerability-input-hint">
<?php
printf(
@ -851,12 +780,13 @@ function wpvulnerability_render_network_admin_tab_notifications() {
</div>
<label>
<input type="hidden" name="wpvulnerability-config[notify][discord]" value="n" />
<input type="checkbox" name="wpvulnerability-config[notify][discord]" value="y" <?php checked( $discord_enabled ); ?> onchange="wpvToggleChannelInput('discord')" />
<?php esc_html_e( 'Discord', 'wpvulnerability' ); ?>
</label>
<div class="wpvulnerability-channel-inputs" id="wpvulnerability-discord-inputs">
<label for="wpvulnerability_discord_webhook"><?php esc_html_e( 'Discord Webhook URL:', 'wpvulnerability' ); ?></label>
<input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[discord_webhook]" id="wpvulnerability_discord_webhook" placeholder="https://discord.com/api/webhooks/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['discord_webhook'] ); ?>" />
<input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[discord_webhook]" id="wpvulnerability_discord_webhook" placeholder="https://discord.com/api/webhooks/..." value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $wpvulnerability_settings['discord_webhook'] ) ? (string) $wpvulnerability_settings['discord_webhook'] : '' ) ); ?>" />
<span class="wpvulnerability-input-hint">
<?php
printf(
@ -869,12 +799,13 @@ function wpvulnerability_render_network_admin_tab_notifications() {
</div>
<label>
<input type="hidden" name="wpvulnerability-config[notify][telegram]" value="n" />
<input type="checkbox" name="wpvulnerability-config[notify][telegram]" value="y" <?php checked( $telegram_enabled ); ?> onchange="wpvToggleChannelInput('telegram')" />
<?php esc_html_e( 'Telegram', 'wpvulnerability' ); ?>
</label>
<div class="wpvulnerability-channel-inputs" id="wpvulnerability-telegram-inputs">
<label for="wpvulnerability_telegram_bot_token"><?php esc_html_e( 'Telegram Bot Token:', 'wpvulnerability' ); ?></label>
<input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[telegram_bot_token]" id="wpvulnerability_telegram_bot_token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" value="<?php echo esc_attr( (string) $wpvulnerability_settings['telegram_bot_token'] ); ?>" />
<input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[telegram_bot_token]" id="wpvulnerability_telegram_bot_token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $wpvulnerability_settings['telegram_bot_token'] ) ? (string) $wpvulnerability_settings['telegram_bot_token'] : '' ) ); ?>" />
<span class="wpvulnerability-input-hint"><?php esc_html_e( 'Format: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', 'wpvulnerability' ); ?></span>
<label for="wpvulnerability_telegram_chat_id" style="margin-top: 12px;"><?php esc_html_e( 'Telegram Chat ID:', 'wpvulnerability' ); ?></label>
@ -917,7 +848,7 @@ function wpvulnerability_render_network_admin_tab_notifications() {
}
function wpvToggleChannelInput(channel) {
var checkbox = document.querySelector('input[name="wpvulnerability-config[notify][' + channel + ']"]');
var checkbox = document.querySelector('input[type="checkbox"][name="wpvulnerability-config[notify][' + channel + ']"]');
var inputs = document.getElementById('wpvulnerability-' + channel + '-inputs');
if (checkbox.checked) {
@ -2193,7 +2124,7 @@ function wpvulnerability_admin_slack_callback() {
$slack_webhook = isset( $wpvulnerability_settings['slack_webhook'] ) ? $wpvulnerability_settings['slack_webhook'] : '';
?>
<input class="regular-text" type="text" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="<?php echo esc_attr( 'https://hooks.slack.com/services/...' ); ?>" value="<?php echo esc_attr( is_scalar( $slack_webhook ) ? (string) $slack_webhook : '' ); ?>" />
<input class="regular-text" type="text" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="<?php echo esc_attr( 'https://hooks.slack.com/services/...' ); ?>" value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $slack_webhook ) ? (string) $slack_webhook : '' ) ); ?>" />
<?php
}
@ -2213,7 +2144,7 @@ function wpvulnerability_admin_teams_callback() {
$teams_webhook = isset( $wpvulnerability_settings['teams_webhook'] ) ? $wpvulnerability_settings['teams_webhook'] : '';
?>
<input class="regular-text" type="text" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="<?php echo esc_attr( 'https://outlook.office.com/webhook/...' ); ?>" value="<?php echo esc_attr( is_scalar( $teams_webhook ) ? (string) $teams_webhook : '' ); ?>" />
<input class="regular-text" type="text" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="<?php echo esc_attr( 'https://outlook.office.com/webhook/...' ); ?>" value="<?php echo esc_attr( wpvulnerability_mask_secret( is_scalar( $teams_webhook ) ? (string) $teams_webhook : '' ) ); ?>" />
<?php
}
@ -2852,8 +2783,12 @@ function wpvulnerability_sanitize_config( $input ) {
// Webhooks.
if ( isset( $input['slack_webhook'] ) ) {
$slack_url = trim( is_scalar( $input['slack_webhook'] ) ? (string) $input['slack_webhook'] : '' );
if ( '' !== $slack_url ) {
$stored_slack = isset( $current_values['slack_webhook'] ) && is_scalar( $current_values['slack_webhook'] ) ? (string) $current_values['slack_webhook'] : '';
$slack_url = trim( is_scalar( $input['slack_webhook'] ) ? (string) $input['slack_webhook'] : '' );
if ( '' !== $slack_url && wpvulnerability_mask_secret( $stored_slack ) === $slack_url ) {
// The field still holds the masked stored value: nothing changed.
$sanitized['slack_webhook'] = $stored_slack;
} elseif ( '' !== $slack_url ) {
$validated_slack = wpvulnerability_validate_webhook_url(
$slack_url,
array( 'hooks.slack.com' )
@ -2875,8 +2810,12 @@ function wpvulnerability_sanitize_config( $input ) {
}
if ( isset( $input['teams_webhook'] ) ) {
$teams_url = trim( is_scalar( $input['teams_webhook'] ) ? (string) $input['teams_webhook'] : '' );
if ( '' !== $teams_url ) {
$stored_teams = isset( $current_values['teams_webhook'] ) && is_scalar( $current_values['teams_webhook'] ) ? (string) $current_values['teams_webhook'] : '';
$teams_url = trim( is_scalar( $input['teams_webhook'] ) ? (string) $input['teams_webhook'] : '' );
if ( '' !== $teams_url && wpvulnerability_mask_secret( $stored_teams ) === $teams_url ) {
// The field still holds the masked stored value: nothing changed.
$sanitized['teams_webhook'] = $stored_teams;
} elseif ( '' !== $teams_url ) {
$validated_teams = wpvulnerability_validate_webhook_url(
$teams_url,
array( 'office.com', 'office365.com', 'api.hooks.microsoft.com' )
@ -2898,8 +2837,12 @@ function wpvulnerability_sanitize_config( $input ) {
}
if ( isset( $input['discord_webhook'] ) ) {
$discord_url = trim( is_scalar( $input['discord_webhook'] ) ? (string) $input['discord_webhook'] : '' );
if ( '' !== $discord_url ) {
$stored_discord = isset( $current_values['discord_webhook'] ) && is_scalar( $current_values['discord_webhook'] ) ? (string) $current_values['discord_webhook'] : '';
$discord_url = trim( is_scalar( $input['discord_webhook'] ) ? (string) $input['discord_webhook'] : '' );
if ( '' !== $discord_url && wpvulnerability_mask_secret( $stored_discord ) === $discord_url ) {
// The field still holds the masked stored value: nothing changed.
$sanitized['discord_webhook'] = $stored_discord;
} elseif ( '' !== $discord_url ) {
$validated_discord = wpvulnerability_validate_webhook_url(
$discord_url,
array( 'discord.com', 'discordapp.com' )
@ -2921,8 +2864,12 @@ function wpvulnerability_sanitize_config( $input ) {
}
if ( isset( $input['telegram_bot_token'] ) ) {
$stored_tg_token = isset( $current_values['telegram_bot_token'] ) && is_scalar( $current_values['telegram_bot_token'] ) ? (string) $current_values['telegram_bot_token'] : '';
$telegram_bot_token = sanitize_text_field( trim( is_scalar( $input['telegram_bot_token'] ) ? (string) $input['telegram_bot_token'] : '' ) );
if ( '' !== $telegram_bot_token ) {
if ( '' !== $telegram_bot_token && wpvulnerability_mask_secret( $stored_tg_token ) === $telegram_bot_token ) {
// The field still holds the masked stored value: nothing changed.
$sanitized['telegram_bot_token'] = $stored_tg_token;
} elseif ( '' !== $telegram_bot_token ) {
if ( ! preg_match( '/^\d+:[A-Za-z0-9_-]+$/', $telegram_bot_token ) ) {
add_settings_error(
'wpvulnerability-config',
@ -2956,7 +2903,7 @@ function wpvulnerability_sanitize_config( $input ) {
*
* @since 3.3.0
*
* @param array<string, mixed> $input Input values.
* @param array<mixed, mixed> $input Input values.
* @return array<string, bool> Sanitized values.
*/
function wpvulnerability_sanitize_analyze( $input ) {
@ -3504,6 +3451,9 @@ function wpvulnerability_render_network_admin_tab_debug() {
// Section 2: Component Detection.
wpvulnerability_render_debug_section_components();
// Section 2b: PHP extensions and system packages.
wpvulnerability_render_debug_section_php_extensions();
// Section 4: Configuration Summary.
wpvulnerability_render_debug_section_config();
@ -4056,22 +4006,30 @@ function wpvulnerability_render_debug_section_api_testing() {
component: component,
nonce: '<?php echo esc_js( wp_create_nonce( 'wpvulnerability_test_api' ) ); ?>'
},
success: function(response) {
if (response.success) {
var result = response.data;
var statusColor = result.success ? '#00a32a' : '#d63638';
var resultHtml = '<div style="border: 1px solid ' + statusColor + '; padding: 15px; margin-top: 10px; border-radius: 4px;">';
resultHtml += '<h4 style="margin-top: 0; color: ' + statusColor + ';">' + component.toUpperCase() + ' - ' + result.message + '</h4>';
resultHtml += '<p><strong><?php echo esc_js( __( 'HTTP Code:', 'wpvulnerability' ) ); ?></strong> ' + result.http_code + '</p>';
resultHtml += '<p><strong><?php echo esc_js( __( 'Response Time:', 'wpvulnerability' ) ); ?></strong> ' + result.response_time + ' ms</p>';
if (result.data_preview) {
resultHtml += '<details style="margin-top: 10px;"><summary style="cursor: pointer; font-weight: 600;"><?php echo esc_js( __( 'Response Preview', 'wpvulnerability' ) ); ?></summary>';
resultHtml += '<pre style="background: #f0f0f0; padding: 10px; overflow-x: auto; margin-top: 10px;">' + result.data_preview + '</pre>';
resultHtml += '</details>';
}
resultHtml += '</div>';
$results.prepend(resultHtml);
success: function(response) {
if (response.success) {
var result = response.data;
var escHtml = function(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
};
var statusColor = result.success ? '#00a32a' : '#d63638';
var resultHtml = '<div style="border: 1px solid ' + statusColor + '; padding: 15px; margin-top: 10px; border-radius: 4px;">';
resultHtml += '<h4 style="margin-top: 0; color: ' + statusColor + ';">' + escHtml(component.toUpperCase()) + ' - ' + escHtml(result.message) + '</h4>';
resultHtml += '<p><strong><?php echo esc_js( __( 'HTTP Code:', 'wpvulnerability' ) ); ?></strong> ' + escHtml(result.http_code) + '</p>';
resultHtml += '<p><strong><?php echo esc_js( __( 'Response Time:', 'wpvulnerability' ) ); ?></strong> ' + escHtml(result.response_time) + ' ms</p>';
if (result.data_preview) {
resultHtml += '<details style="margin-top: 10px;"><summary style="cursor: pointer; font-weight: 600;"><?php echo esc_js( __( 'Response Preview', 'wpvulnerability' ) ); ?></summary>';
resultHtml += '<pre style="background: #f0f0f0; padding: 10px; overflow-x: auto; margin-top: 10px;">' + escHtml(result.data_preview) + '</pre>';
resultHtml += '</details>';
}
resultHtml += '</div>';
$results.prepend(resultHtml);
}
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Test', 'wpvulnerability' ) ); ?> ' + component.charAt(0).toUpperCase() + component.slice(1));
},
error: function() {