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

@ -19,14 +19,23 @@
$wpvulnerability_settings = get_option( 'wpvulnerability-config' );
$wpvulnerability_analyze = get_option( 'wpvulnerability-analyze' );
/**
* Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages.
*
* @since 2.0.0
*
* @return void
*/
function wpvulnerability_admin_enqueue_scripts() {
/**
* 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( $hook ) {
// Load on the 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;
}
wp_enqueue_style(
'wpvulnerability-admin',
WPVULNERABILITY_PLUGIN_URL . 'assets/admin.css',
@ -143,7 +152,7 @@ if ( isset( $_POST['wpvulnerability_delete_on_uninstall'] ) && check_admin_refer
}
$wpvulnerability_settings['delete_on_uninstall'] = isset( $_POST['delete_on_uninstall'] ) ? 1 : 0;
update_option( 'wpvulnerability-config', $wpvulnerability_settings );
add_settings_error( 'wpvulnerability-tools', 'uninstall-saved', __( 'Uninstall preference saved.', 'wpvulnerability' ), 'success' );
set_transient( 'wpvulnerability_message_manual_success', __( 'Uninstall preference saved.', 'wpvulnerability' ), 10 );
}
}
@ -532,6 +541,7 @@ function wpvulnerability_render_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>
@ -542,12 +552,13 @@ function wpvulnerability_render_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(
@ -560,12 +571,13 @@ function wpvulnerability_render_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(
@ -578,12 +590,13 @@ function wpvulnerability_render_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(
@ -596,12 +609,13 @@ function wpvulnerability_render_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>
@ -644,7 +658,7 @@ function wpvulnerability_render_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) {
@ -2040,8 +2054,8 @@ 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 : '' ); ?>" />
<?php
<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
}
/**
@ -2060,8 +2074,8 @@ 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 : '' ); ?>" />
<?php
<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
}
/**
@ -2279,8 +2293,12 @@ function wpvulnerability_admin_sanitize( $input ) {
}
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_values['slack_webhook'] = $stored_slack;
} elseif ( '' !== $slack_url ) {
$validated_slack = wpvulnerability_validate_webhook_url(
$slack_url,
array( 'hooks.slack.com' )
@ -2302,8 +2320,12 @@ function wpvulnerability_admin_sanitize( $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_values['teams_webhook'] = $stored_teams;
} elseif ( '' !== $teams_url ) {
$validated_teams = wpvulnerability_validate_webhook_url(
$teams_url,
array( 'office.com', 'office365.com', 'api.hooks.microsoft.com' )
@ -2325,8 +2347,12 @@ function wpvulnerability_admin_sanitize( $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_values['discord_webhook'] = $stored_discord;
} elseif ( '' !== $discord_url ) {
$validated_discord = wpvulnerability_validate_webhook_url(
$discord_url,
array( 'discord.com', 'discordapp.com' )
@ -2348,8 +2374,12 @@ function wpvulnerability_admin_sanitize( $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_values['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',
@ -2869,6 +2899,7 @@ function wpvulnerability_admin_init() {
array(
'sanitize_callback' => 'wpvulnerability_admin_sanitize',
'default' => array(),
'show_in_rest' => false,
)
);
@ -2941,6 +2972,7 @@ function wpvulnerability_admin_init() {
array(
'sanitize_callback' => 'wpvulnerability_analyze_sanitize',
'default' => array(),
'show_in_rest' => false,
)
);
@ -3381,6 +3413,9 @@ function wpvulnerability_render_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();
@ -3897,22 +3932,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() {