This commit is contained in:
Javier Casares 2026-07-09 06:28:55 +00:00
commit db98c4438c
12 changed files with 123 additions and 133 deletions

View file

@ -1,5 +1,25 @@
== Changelog ==
= [5.1.1] - 2026-07-09 =
**Fixed**
* Site Health: the memcached, Redis, and SQLite vulnerability tests always returned "Invalid software type" because those components were missing from the software list. They now run correctly.
* "Send test email" failed when no email recipients were configured: the forced-test path added the admin email but the send gate used a flag computed before the override. The test email now sends.
* "Run notification now" reported failure when only webhook channels (Slack/Teams/Discord/Telegram) were enabled and email was disabled. The notification result now reflects whether any channel delivered successfully.
* Weekly notification cron events were never auto-scheduled, because the `weekly` cron schedule was registered only at `init` while the on-load scheduling runs earlier. The weekly/daily schedule registration moved to the always-loaded scheduler so weekly notifications schedule correctly.
* Deactivation deleted the per-component analysis settings (`wpvulnerability-analyze`), so deactivating and reactivating reset which components were hidden. Analysis settings are now preserved on deactivation (only uninstall removes them).
* The debug "Cron Status" panel always showed the notification event as "not scheduled" because it checked the wrong hook name (`wpvulnerability_send_notification` instead of `wpvulnerability_notification`).
* The debug "last run" timestamp was always empty because it read a `wpvulnerability-logs` option that is never written; it now reads the most recent API log entry.
* Redis detection called `close()` twice on the success path (once in the `try` block, once in `finally`); the redundant close was removed.
* The single-site vulnerable-themes count was hardcoded to `0` instead of reading the stored option.
**Changed**
* Single-site email recipient sanitization now uses `is_email()` for strict validation, matching the multisite behaviour.
* Removed a redundant nonce field from the single-site "Reset Plugin" form (the form only submits the full-reset action).
* Removed dead code: the unused `wpvulnerability_sanitize_messages` callback and its setting registration, and an unused `$tools_action` variable in the multisite admin.
= [5.1.0] - 2026-07-08 =
**Security**

View file

@ -3,9 +3,9 @@ Contributors: javiercasares, davidperez, lbonomo, alexclassroom
Tags: security, vulnerability, site-health
Requires at least: 5.6
Tested up to: 7.1
Stable tag: 5.1.0
Stable tag: 5.1.1
Requires PHP: 7.0
Version: 5.1.0
Version: 5.1.1
License: GPL-3.0-or-later
License URI: https://spdx.org/licenses/GPL-3.0-or-later.html
@ -201,6 +201,26 @@ First of all, peace of mind. Investigate what the vulnerability is and, above al
== Changelog ==
= [5.1.1] - 2026-07-09 =
**Fixed**
* Site Health: the memcached, Redis, and SQLite vulnerability tests always returned "Invalid software type" because those components were missing from the software list. They now run correctly.
* "Send test email" failed when no email recipients were configured: the forced-test path added the admin email but the send gate used a flag computed before the override. The test email now sends.
* "Run notification now" reported failure when only webhook channels (Slack/Teams/Discord/Telegram) were enabled and email was disabled. The notification result now reflects whether any channel delivered successfully.
* Weekly notification cron events were never auto-scheduled, because the `weekly` cron schedule was registered only at `init` while the on-load scheduling runs earlier. The weekly/daily schedule registration moved to the always-loaded scheduler so weekly notifications schedule correctly.
* Deactivation deleted the per-component analysis settings (`wpvulnerability-analyze`), so deactivating and reactivating reset which components were hidden. Analysis settings are now preserved on deactivation (only uninstall removes them).
* The debug "Cron Status" panel always showed the notification event as "not scheduled" because it checked the wrong hook name (`wpvulnerability_send_notification` instead of `wpvulnerability_notification`).
* The debug "last run" timestamp was always empty because it read a `wpvulnerability-logs` option that is never written; it now reads the most recent API log entry.
* Redis detection called `close()` twice on the success path (once in the `try` block, once in `finally`); the redundant close was removed.
* The single-site vulnerable-themes count was hardcoded to `0` instead of reading the stored option.
**Changed**
* Single-site email recipient sanitization now uses `is_email()` for strict validation, matching the multisite behaviour.
* Removed a redundant nonce field from the single-site "Reset Plugin" form (the form only submits the full-reset action).
* Removed dead code: the unused `wpvulnerability_sanitize_messages` callback and its setting registration, and an unused `$tools_action` variable in the multisite admin.
= [5.1.0] - 2026-07-08 =
**Security**

View file

@ -1437,7 +1437,6 @@ function wpvulnerability_render_admin_tab_tools() {
<strong style="color: #b32d2e;"><?php esc_html_e( 'This action cannot be undone.', 'wpvulnerability' ); ?></strong>
</p>
<form method="post" action="<?php echo esc_url( $tools_action ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to reset all WPVulnerability data? This action cannot be undone.', 'wpvulnerability' ) ); ?>');">
<?php wp_nonce_field( 'wpvulnerability_reset_action', 'wpvulnerability_reset_nonce' ); ?>
<?php wp_nonce_field( 'wpvulnerability_full_reset_action', 'wpvulnerability_full_reset_nonce' ); ?>
<input type="submit" name="wpvulnerability_full_reset" value="<?php esc_attr_e( 'Reset Plugin', 'wpvulnerability' ); ?>" class="button button-delete">
</form>
@ -2211,7 +2210,7 @@ function wpvulnerability_admin_sanitize( $input ) {
foreach ( $input_email_text as $input_email ) {
$input_email = sanitize_email( trim( (string) $input_email ) );
if ( $input_email ) {
if ( is_email( $input_email ) ) {
$input_emails[] = $input_email;
}
}

View file

@ -1447,14 +1447,6 @@ function wpvulnerability_render_network_admin_tab_logs() {
function wpvulnerability_render_network_admin_tab_tools() {
global $wpvulnerability_settings;
$tools_action = add_query_arg(
array(
'page' => 'wpvulnerability-options',
'tab' => 'tools',
),
network_admin_url( 'settings.php' )
);
$wpvulnerability_cron_config = is_multisite() ? get_site_option( 'wpvulnerability-config', array() ) : get_option( 'wpvulnerability-config', array() );
if ( ! is_array( $wpvulnerability_cron_config ) ) {
$wpvulnerability_cron_config = array();
@ -2959,24 +2951,6 @@ function wpvulnerability_sanitize_config( $input ) {
return $sanitized;
}
/**
* Sanitizes the messages generated by the plugin (simple messages).
*
* @since 2.0.0
*
* @param array<string, mixed> $input Input values.
* @return array<string, string> Sanitized values.
*/
function wpvulnerability_sanitize_messages( $input ) {
$sanitized = array();
foreach ( $input as $key => $message ) {
$sanitized[ sanitize_key( (string) $key ) ] = sanitize_text_field( is_scalar( $message ) ? (string) $message : '' );
}
return $sanitized;
}
/**
* Strictly sanitizes the analysis options (booleans).
*
@ -3037,18 +3011,6 @@ function wpvulnerability_admin_init() {
)
);
// Register messages.
register_setting(
'admin_wpvulnerability_settings',
'wpvulnerability-messages',
array(
'sanitize_callback' => 'wpvulnerability_sanitize_messages',
'default' => array(),
'show_in_rest' => false,
'type' => 'array',
)
);
// Add a section to the settings page.
add_settings_section(
'admin_wpvulnerability_settings',

View file

@ -494,7 +494,7 @@ function wpvulnerability_debug_get_cron_status() {
'scheduled' => false,
),
'send_notification' => array(
'hook' => 'wpvulnerability_send_notification',
'hook' => 'wpvulnerability_notification',
'next_run' => null,
'last_run' => null,
'scheduled' => false,
@ -509,28 +509,28 @@ function wpvulnerability_debug_get_cron_status() {
}
// Check notification cron.
$next_notification = wp_next_scheduled( 'wpvulnerability_send_notification' );
$next_notification = wp_next_scheduled( 'wpvulnerability_notification' );
if ( $next_notification ) {
$cron_status['send_notification']['next_run'] = $next_notification;
$cron_status['send_notification']['scheduled'] = true;
}
// Try to get last run times from logs.
$logs_raw = is_multisite() ? get_site_option( 'wpvulnerability-logs', array() ) : get_option( 'wpvulnerability-logs', array() );
$logs = is_array( $logs_raw ) ? $logs_raw : array();
if ( ! empty( $logs ) ) {
// Get the most recent log entry for each type.
foreach ( array_reverse( $logs ) as $log ) {
if ( ! is_array( $log ) ) {
continue; }
if ( isset( $log['time'] ) && isset( $log['url'] ) ) {
$timestamp = $log['time'];
// Check if this is an update-related log.
if ( ! $cron_status['update_database']['last_run'] ) {
$cron_status['update_database']['last_run'] = $timestamp;
}
}
// Last run = most recent API response log (stored as the wpvulnerability_log CPT).
$last_log = get_posts(
array(
'post_type' => 'wpvulnerability_log',
'post_status' => 'any',
'posts_per_page' => 1,
'orderby' => 'date',
'order' => 'DESC',
'fields' => 'ids',
'no_found_rows' => true,
)
);
if ( ! empty( $last_log ) ) {
$timestamp = get_post_timestamp( $last_log[0] );
if ( false !== $timestamp ) {
$cron_status['update_database']['last_run'] = $timestamp;
}
}

View file

@ -1070,10 +1070,6 @@ function wpvulnerability_detect_redis() {
$result['method'] = 'redis_extension';
$result['reliability'] = 90;
if ( $temporary_connection ) {
$redis_client->close();
}
return $result;
}
}

View file

@ -9,60 +9,6 @@
defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
/**
* Adds a custom schedule for a weekly cron job.
*
* This function adds a new schedule interval of one week (604800 seconds)
* to the system's available cron schedules. It allows tasks to be scheduled
* to run every week using the 'weekly' interval.
*
* @since 2.0.0
*
* @param array<string, array<string, int|string>> $schedules The existing system schedules.
*
* @return array<string, array<string, int|string>> The updated list of schedules with the added weekly interval.
*/
function wpvulnerability_add_every_week( $schedules ) {
// Add a weekly schedule interval of 604800 seconds (1 week).
// Use did_action('init') guard: cron_schedules fires before init (WP 6.7+ textdomain notice).
$schedules['weekly'] = array(
'interval' => 604800,
'display' => did_action( 'init' ) ? __( 'Every week', 'wpvulnerability' ) : 'Every week',
);
// Return the modified list of schedules.
return $schedules;
}
// Hook the function to the 'cron_schedules' filter to add the custom schedule.
add_filter( 'cron_schedules', 'wpvulnerability_add_every_week' );
/**
* Adds a custom schedule for daily events.
*
* This function adds a new schedule interval of one day (86400 seconds)
* to the system's available cron schedules. It allows tasks to be scheduled
* to run every day using the 'daily' interval.
*
* @since 2.0.0
*
* @param array<string, array<string, int|string>> $schedules List of available schedules.
*
* @return array<string, array<string, int|string>> Modified list of available schedules with the added daily interval.
*/
function wpvulnerability_add_every_day( $schedules ) {
// Define a new schedule with a 24 hour interval.
// Use did_action('init') guard: cron_schedules fires before init (WP 6.7+ textdomain notice).
$schedules['daily'] = array(
'interval' => 86400,
'display' => did_action( 'init' ) ? __( 'Every day', 'wpvulnerability' ) : 'Every day',
);
// Return the modified list of schedules.
return $schedules;
}
// Hook the function to the 'cron_schedules' filter to add the custom schedule.
add_filter( 'cron_schedules', 'wpvulnerability_add_every_day' );
/**
* Disables email notifications when requested via URL.
*
@ -715,7 +661,7 @@ function wpvulnerability_execute_notification( $forced = false ) {
$wpmail = false;
if ( $email_enabled ) {
if ( $email_enabled || $forced ) {
$mail_to = is_array( $wpvulnerability_settings['emails'] ) ? array_map(
static function ( $e ) {
return is_scalar( $e ) ? (string) $e : '';
@ -728,23 +674,27 @@ function wpvulnerability_execute_notification( $forced = false ) {
$text_message_body = wpvulnerability_html_to_plain_text( $email_content );
$text_message = trim( (string) ( $email_subject . "\n\n" . $text_message_body ) );
if ( $slack_enabled ) {
wpvulnerability_send_slack_notification( ( is_scalar( $wpvulnerability_settings['slack_webhook'] ) ? (string) $wpvulnerability_settings['slack_webhook'] : '' ), $text_message );
// Track whether any webhook channel delivered successfully, so a webhook-only
// notification (email disabled) is not reported as a failure.
$webhook_sent = false;
if ( $slack_enabled && wpvulnerability_send_slack_notification( ( is_scalar( $wpvulnerability_settings['slack_webhook'] ) ? (string) $wpvulnerability_settings['slack_webhook'] : '' ), $text_message ) ) {
$webhook_sent = true;
}
if ( $teams_enabled ) {
wpvulnerability_send_teams_notification( ( is_scalar( $wpvulnerability_settings['teams_webhook'] ) ? (string) $wpvulnerability_settings['teams_webhook'] : '' ), $text_message );
if ( $teams_enabled && wpvulnerability_send_teams_notification( ( is_scalar( $wpvulnerability_settings['teams_webhook'] ) ? (string) $wpvulnerability_settings['teams_webhook'] : '' ), $text_message ) ) {
$webhook_sent = true;
}
if ( $discord_enabled ) {
wpvulnerability_send_discord_notification( ( is_scalar( $wpvulnerability_settings['discord_webhook'] ) ? (string) $wpvulnerability_settings['discord_webhook'] : '' ), $text_message );
if ( $discord_enabled && wpvulnerability_send_discord_notification( ( is_scalar( $wpvulnerability_settings['discord_webhook'] ) ? (string) $wpvulnerability_settings['discord_webhook'] : '' ), $text_message ) ) {
$webhook_sent = true;
}
if ( $telegram_enabled ) {
wpvulnerability_send_telegram_notification( ( is_scalar( $wpvulnerability_settings['telegram_bot_token'] ) ? (string) $wpvulnerability_settings['telegram_bot_token'] : '' ), ( is_scalar( $wpvulnerability_settings['telegram_chat_id'] ) ? (string) $wpvulnerability_settings['telegram_chat_id'] : '' ), $text_message );
if ( $telegram_enabled && wpvulnerability_send_telegram_notification( ( is_scalar( $wpvulnerability_settings['telegram_bot_token'] ) ? (string) $wpvulnerability_settings['telegram_bot_token'] : '' ), ( is_scalar( $wpvulnerability_settings['telegram_chat_id'] ) ? (string) $wpvulnerability_settings['telegram_chat_id'] : '' ), $text_message ) ) {
$webhook_sent = true;
}
return $wpmail;
return $wpmail || $webhook_sent;
}
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- DOM properties follow upstream naming.

View file

@ -365,7 +365,6 @@ function wpvulnerability_deactivation() {
$options = array(
'wpvulnerability_settings',
'wpvulnerability-data',
'wpvulnerability-analyze',
'wpvulnerability-themes',
'wpvulnerability-themes-cache',
'wpvulnerability-themes-vulnerable',

View file

@ -37,6 +37,47 @@ function wpvulnerability_add_every_six_hours( $schedules ) {
return $schedules;
}
// Add weekly and daily schedules used by the notification cron.
// These must be registered in this file (always loaded pre-init) so that the
// on-load notification scheduling at the bottom of this file can resolve the
// 'weekly' schedule before notifications.php is loaded at init.
add_filter( 'cron_schedules', 'wpvulnerability_add_every_week' );
add_filter( 'cron_schedules', 'wpvulnerability_add_every_day' );
/**
* Registers a custom weekly cron schedule.
*
* @since 2.0.0
*
* @param array<string, array<string, int|string>> $schedules Existing schedules.
* @return array<string, array<string, int|string>> Schedules with the weekly interval added.
*/
function wpvulnerability_add_every_week( $schedules ) {
$schedules['weekly'] = array(
'interval' => 604800,
'display' => did_action( 'init' ) ? __( 'Every week', 'wpvulnerability' ) : 'Every week',
);
return $schedules;
}
/**
* Registers a custom daily cron schedule.
*
* @since 2.0.0
*
* @param array<string, array<string, int|string>> $schedules Existing schedules.
* @return array<string, array<string, int|string>> Schedules with the daily interval added.
*/
function wpvulnerability_add_every_day( $schedules ) {
$schedules['daily'] = array(
'interval' => 86400,
'display' => did_action( 'init' ) ? __( 'Every day', 'wpvulnerability' ) : 'Every day',
);
return $schedules;
}
// Remove legacy scheduled events on subsites in multisite installs.
if ( is_multisite() && ! is_main_site() ) {
wpvulnerability_clear_plugin_cron_hooks();

View file

@ -182,6 +182,9 @@ function wpvulnerability_test_software( $software ) {
'mysql' => __( 'MySQL', 'wpvulnerability' ),
'imagemagick' => __( 'ImageMagick', 'wpvulnerability' ),
'curl' => __( 'curl', 'wpvulnerability' ),
'memcached' => __( 'memcached', 'wpvulnerability' ),
'redis' => __( 'Redis', 'wpvulnerability' ),
'sqlite' => __( 'SQLite', 'wpvulnerability' ),
);
if ( ! array_key_exists( $software, $software_list ) ) {

View file

@ -687,7 +687,7 @@ function wpvulnerability_themes_view( $views ) {
return $views;
}
$raw_count = is_multisite() ? get_site_option( 'wpvulnerability-themes-vulnerable', '0' ) : '0';
$raw_count = is_multisite() ? get_site_option( 'wpvulnerability-themes-vulnerable', '0' ) : get_option( 'wpvulnerability-themes-vulnerable', '0' );
$wpvulnerability_themes_total = ( is_scalar( json_decode( is_string( $raw_count ) ? $raw_count : '0', true ) ) ? (int) json_decode( is_string( $raw_count ) ? $raw_count : '0', true ) : 0 );
if ( is_multisite() && is_network_admin() ) {

View file

@ -5,7 +5,7 @@
* Description: Receive information about possible vulnerabilities in your WordPress from WordPress Vulnerability Database API.
* Requires at least: 5.6
* Requires PHP: 7.0
* Version: 5.1.0
* Version: 5.1.1
* Author: ROBOTSTXT
* Author URI: https://www.robotstxt.es/
* License: GPL-3.0-or-later
@ -16,7 +16,7 @@
*
* @package WPVulnerability
*
* @version 5.1.0
* @version 5.1.1
*/
defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
@ -24,7 +24,7 @@ defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
/**
* Set some constants that I can change in future versions.
*/
define( 'WPVULNERABILITY_PLUGIN_VERSION', '5.1.0' );
define( 'WPVULNERABILITY_PLUGIN_VERSION', '5.1.1' );
define( 'WPVULNERABILITY_API_HOST', 'https://www.wpvulnerability.net/' );
/**