From db98c4438cd1231c62c19871fd30acfb39e730e6 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Thu, 9 Jul 2026 06:28:55 +0000 Subject: [PATCH] v5.1.1 --- changelog.txt | 20 ++++++++ readme.txt | 24 +++++++++- wpvulnerability-admin.php | 3 +- wpvulnerability-adminms.php | 38 --------------- wpvulnerability-debug.php | 36 +++++++------- wpvulnerability-general.php | 4 -- wpvulnerability-notifications.php | 78 ++++++------------------------- wpvulnerability-run.php | 1 - wpvulnerability-schedule.php | 41 ++++++++++++++++ wpvulnerability-sitehealth.php | 3 ++ wpvulnerability-themes.php | 2 +- wpvulnerability.php | 6 +-- 12 files changed, 123 insertions(+), 133 deletions(-) diff --git a/changelog.txt b/changelog.txt index 854cfb8..975fa5c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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** diff --git a/readme.txt b/readme.txt index cf3f792..8e27bd3 100644 --- a/readme.txt +++ b/readme.txt @@ -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** diff --git a/wpvulnerability-admin.php b/wpvulnerability-admin.php index 51e5852..1159d05 100644 --- a/wpvulnerability-admin.php +++ b/wpvulnerability-admin.php @@ -1437,7 +1437,6 @@ function wpvulnerability_render_admin_tab_tools() {

-
@@ -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; } } diff --git a/wpvulnerability-adminms.php b/wpvulnerability-adminms.php index 1867698..0dc81f9 100644 --- a/wpvulnerability-adminms.php +++ b/wpvulnerability-adminms.php @@ -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 $input Input values. - * @return array 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', diff --git a/wpvulnerability-debug.php b/wpvulnerability-debug.php index b7a9da4..459ebe9 100644 --- a/wpvulnerability-debug.php +++ b/wpvulnerability-debug.php @@ -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; } } diff --git a/wpvulnerability-general.php b/wpvulnerability-general.php index 9a21e43..17884c5 100644 --- a/wpvulnerability-general.php +++ b/wpvulnerability-general.php @@ -1070,10 +1070,6 @@ function wpvulnerability_detect_redis() { $result['method'] = 'redis_extension'; $result['reliability'] = 90; - if ( $temporary_connection ) { - $redis_client->close(); - } - return $result; } } diff --git a/wpvulnerability-notifications.php b/wpvulnerability-notifications.php index 1fda631..981e5fa 100644 --- a/wpvulnerability-notifications.php +++ b/wpvulnerability-notifications.php @@ -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> $schedules The existing system schedules. - * - * @return array> 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> $schedules List of available schedules. - * - * @return array> 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. diff --git a/wpvulnerability-run.php b/wpvulnerability-run.php index 707d30d..147b2fa 100644 --- a/wpvulnerability-run.php +++ b/wpvulnerability-run.php @@ -365,7 +365,6 @@ function wpvulnerability_deactivation() { $options = array( 'wpvulnerability_settings', 'wpvulnerability-data', - 'wpvulnerability-analyze', 'wpvulnerability-themes', 'wpvulnerability-themes-cache', 'wpvulnerability-themes-vulnerable', diff --git a/wpvulnerability-schedule.php b/wpvulnerability-schedule.php index 5916b2d..c409f92 100644 --- a/wpvulnerability-schedule.php +++ b/wpvulnerability-schedule.php @@ -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> $schedules Existing schedules. + * @return array> 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> $schedules Existing schedules. + * @return array> 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(); diff --git a/wpvulnerability-sitehealth.php b/wpvulnerability-sitehealth.php index 295b121..1834667 100644 --- a/wpvulnerability-sitehealth.php +++ b/wpvulnerability-sitehealth.php @@ -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 ) ) { diff --git a/wpvulnerability-themes.php b/wpvulnerability-themes.php index f3819d1..540ad83 100644 --- a/wpvulnerability-themes.php +++ b/wpvulnerability-themes.php @@ -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() ) { diff --git a/wpvulnerability.php b/wpvulnerability.php index 7a50f3f..d4aabeb 100644 --- a/wpvulnerability.php +++ b/wpvulnerability.php @@ -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/' ); /**