$links The links that appear in the plugin row. * * @return array The modified array of links. */ function wpvulnerability_add_settings_link( $links ) { // Check if the user has the required capabilities to view the settings link. if ( wpvulnerability_capabilities() ) { // Determine the correct settings link based on the environment. if ( is_multisite() && is_network_admin() ) { // Network admin settings link for multisite. $links[] = '' . __( 'Network Settings', 'wpvulnerability' ) . ''; } elseif ( ! is_multisite() && is_admin() ) { // Standard settings link for single site. $links[] = '' . __( 'Settings', 'wpvulnerability' ) . ''; } } return $links; } // Hook the function to the appropriate filters. if ( is_multisite() ) { add_filter( 'network_admin_plugin_action_links_' . WPVULNERABILITY_PLUGIN_BASE, 'wpvulnerability_add_settings_link' ); } else { add_filter( 'plugin_action_links_' . WPVULNERABILITY_PLUGIN_BASE, 'wpvulnerability_add_settings_link' ); } /** * Updates the plugin's vulnerability data. * * This function updates the vulnerability data for WordPress core, plugins, themes, PHP, Apache, nginx, MariaDB, and MySQL. * It ensures that the required functions are available by including the necessary files. * After updating the vulnerabilities, it flushes the WordPress cache. * * @since 2.0.0 * * @return void */ function wpvulnerability_update_database_data() { // Ensure necessary files are included for core, plugins, and themes. require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php'; wpvulnerability_delete_transients(); // Update core, plugins, and themes vulnerabilities. wpvulnerability_core_get_vulnerabilities_clean(); wpvulnerability_plugin_get_vulnerabilities_clean(); wpvulnerability_theme_get_vulnerabilities_clean(); // Array of software types to update. $software_types = array( 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' ); // Update vulnerabilities for each software type. foreach ( $software_types as $software ) { wpvulnerability_get_vulnerabilities_clean( $software ); } wpvulnerability_statistics_get(); } /** * Updates the plugin's vulnerability data if the cache has expired. * * This function checks if the cached vulnerability data for various components (core, plugins, themes, PHP, Apache, nginx, MariaDB, MySQL) has expired and updates it accordingly. * It ensures that the required functions are available by including the necessary files. * The function handles both multisite and single site installations. * * @since 3.0.0 * * @return void */ function wpvulnerability_expired_database_data() { // Ensure necessary files are included for core, plugins, and themes. require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php'; require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php'; // Current time for cache expiration comparison. $cache_time = time(); // Check and update core, plugins, and themes vulnerabilities if cache has expired. $components = array( 'core' => 'wpvulnerability-core-cache', 'plugin' => 'wpvulnerability-plugins-cache', 'theme' => 'wpvulnerability-themes-cache', ); foreach ( $components as $component => $cache_key ) { $cache_value = is_multisite() ? get_site_option( $cache_key ) : get_option( $cache_key ); if ( json_decode( is_string( $cache_value ) ? $cache_value : '' ) < $cache_time ) { call_user_func( "wpvulnerability_{$component}_get_vulnerabilities_clean" ); } } // Array of software types to update. $software_types = array( 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' ); // Ensure necessary files are included and update vulnerabilities for each software type. foreach ( $software_types as $software ) { if ( is_multisite() ) { $site_opt = get_site_option( 'wpvulnerability-' . $software . '-cache' ); if ( json_decode( is_string( $site_opt ) ? $site_opt : '' ) < $cache_time ) { wpvulnerability_get_vulnerabilities_clean( $software ); } } else { $opt = get_option( 'wpvulnerability-' . $software . '-cache' ); if ( json_decode( is_string( $opt ) ? $opt : '' ) < $cache_time ) { wpvulnerability_get_vulnerabilities_clean( $software ); } } } $statistics_cache_raw = is_multisite() ? get_site_option( 'wpvulnerability-statistics-cache' ) : get_option( 'wpvulnerability-statistics-cache' ); if ( is_numeric( $statistics_cache_raw ) ) { $statistics_cache = (int) $statistics_cache_raw; } elseif ( is_string( $statistics_cache_raw ) ) { $decoded_cache = json_decode( $statistics_cache_raw ); $statistics_cache = is_numeric( $decoded_cache ) ? (int) $decoded_cache : 0; } else { $statistics_cache = 0; } if ( $statistics_cache < $cache_time ) { wpvulnerability_statistics_get(); } unset( $cache_time, $statistics_cache, $statistics_cache_raw ); } /** * Initializes persistent plugin data, ensures required options exist, and tracks * initialization metadata so upgrades provision new defaults when needed. * * @since 4.1.2 * * @param bool $is_real_activation Optional. Whether the routine runs during the activation hook. Default false. * * @return void */ function wpvulnerability_initialize_plugin_data( $is_real_activation = false ) { $is_multisite = is_multisite(); $config_key = $is_multisite ? 'get_site_option' : 'get_option'; $add_option = $is_multisite ? 'add_site_option' : 'add_option'; $update_option = $is_multisite ? 'update_site_option' : 'update_option'; $initialized_raw = $config_key( 'wpvulnerability_initialized' ); $initialized = array( 'timestamp' => 0, 'version' => '', ); if ( is_array( $initialized_raw ) ) { if ( isset( $initialized_raw['timestamp'] ) && is_numeric( $initialized_raw['timestamp'] ) ) { $initialized['timestamp'] = (int) $initialized_raw['timestamp']; } if ( isset( $initialized_raw['version'] ) && is_string( $initialized_raw['version'] ) ) { $initialized['version'] = $initialized_raw['version']; } } elseif ( is_numeric( $initialized_raw ) ) { $initialized['timestamp'] = (int) $initialized_raw; } $needs_upgrade = version_compare( (string) $initialized['version'], WPVULNERABILITY_PLUGIN_VERSION, '<' ); if ( ! $is_real_activation && ! empty( $initialized_raw ) && ! $needs_upgrade ) { return; } if ( $is_real_activation ) { wpvulnerability_delete_transients(); } // Add wpvulnerability-config option if it does not exist. if ( ! $config_key( 'wpvulnerability-config' ) ) { $default_config = array( 'emails' => get_bloginfo( 'admin_email' ), 'period' => 'weekly', 'day' => 'monday', 'hour' => 0, 'minute' => 0, 'cache' => 12, 'log_retention' => 0, 'delete_on_uninstall' => 0, 'notify' => array( 'email' => 'y', 'slack' => 'n', 'teams' => 'n', ), 'slack_webhook' => '', 'teams_webhook' => '', ); $add_option( 'wpvulnerability-config', $default_config ); } // Add other options if they do not exist. $options = array( 'wpvulnerability-plugins' => '', 'wpvulnerability-plugins-cache' => 0, 'wpvulnerability-plugins-vulnerable' => 0, 'wpvulnerability-plugins-data' => '', 'wpvulnerability-plugins-data-cache' => 0, 'wpvulnerability-themes' => '', 'wpvulnerability-themes-cache' => 0, 'wpvulnerability-themes-vulnerable' => 0, 'wpvulnerability-core' => '', 'wpvulnerability-core-cache' => 0, 'wpvulnerability-core-vulnerable' => 0, 'wpvulnerability-php' => '', 'wpvulnerability-php-cache' => 0, 'wpvulnerability-php-vulnerable' => 0, 'wpvulnerability-apache' => '', 'wpvulnerability-apache-cache' => 0, 'wpvulnerability-apache-vulnerable' => 0, 'wpvulnerability-nginx' => '', 'wpvulnerability-nginx-cache' => 0, 'wpvulnerability-nginx-vulnerable' => 0, 'wpvulnerability-mariadb' => '', 'wpvulnerability-mariadb-cache' => 0, 'wpvulnerability-mariadb-vulnerable' => 0, 'wpvulnerability-mysql' => '', 'wpvulnerability-mysql-cache' => 0, 'wpvulnerability-mysql-vulnerable' => 0, 'wpvulnerability-imagemagick' => '', 'wpvulnerability-imagemagick-cache' => 0, 'wpvulnerability-imagemagick-vulnerable' => 0, 'wpvulnerability-curl' => '', 'wpvulnerability-curl-cache' => 0, 'wpvulnerability-curl-vulnerable' => 0, 'wpvulnerability-memcached' => '', 'wpvulnerability-memcached-cache' => 0, 'wpvulnerability-memcached-vulnerable' => 0, 'wpvulnerability-redis' => '', 'wpvulnerability-redis-cache' => 0, 'wpvulnerability-redis-vulnerable' => 0, 'wpvulnerability-sqlite' => '', 'wpvulnerability-sqlite-cache' => 0, 'wpvulnerability-sqlite-vulnerable' => 0, 'wpvulnerability-statistics' => '', 'wpvulnerability-statistics-cache' => 0, 'wpvulnerability_initialized' => 0, ); // Large data blobs should not autoload on every WP request (single-site only; // add_site_option does not support autoload control). $no_autoload_keys = array( 'wpvulnerability-plugins', 'wpvulnerability-plugins-data', 'wpvulnerability-themes', 'wpvulnerability-core', 'wpvulnerability-php', 'wpvulnerability-apache', 'wpvulnerability-nginx', 'wpvulnerability-mariadb', 'wpvulnerability-mysql', 'wpvulnerability-imagemagick', 'wpvulnerability-curl', 'wpvulnerability-memcached', 'wpvulnerability-redis', 'wpvulnerability-sqlite', 'wpvulnerability-statistics', ); foreach ( $options as $key => $value ) { if ( ! $config_key( $key ) ) { if ( ! $is_multisite && in_array( $key, $no_autoload_keys, true ) ) { add_option( $key, $value, '', false ); } else { $add_option( $key, $value ); } } } // Add wpvulnerability-analyze option if it does not exist. if ( ! $config_key( 'wpvulnerability-analyze' ) ) { $default_analyze = 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, ); foreach ( array_keys( $default_analyze ) as $component ) { $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $component ); if ( defined( $constant ) && constant( $constant ) ) { $default_analyze[ $component ] = 1; } } $current_option_raw = $config_key( 'wpvulnerability-analyze' ); $current_option = is_array( $current_option_raw ) ? $current_option_raw : false; if ( false === $current_option ) { $add_option( 'wpvulnerability-analyze', $default_analyze ); } else { $updated_option = array_merge( $default_analyze, $current_option ); $update_option( 'wpvulnerability-analyze', $updated_option ); } } $update_option( 'wpvulnerability_initialized', array( 'timestamp' => (int) time(), 'version' => WPVULNERABILITY_PLUGIN_VERSION, ) ); } /** * Callback function for when the plugin is activated. * Adds plugin data options if they are not already created. * * @since 2.0.0 * * @return void */ function wpvulnerability_activation() { wpvulnerability_initialize_plugin_data( true ); } /** * Callback function to run when the plugin is deactivated. * Deletes options and removes scheduled wp-cron jobs. * * @since 2.0.0 * * @return void */ function wpvulnerability_deactivation() { $options = array( 'wpvulnerability_settings', 'wpvulnerability-data', 'wpvulnerability-themes', 'wpvulnerability-themes-cache', 'wpvulnerability-themes-vulnerable', 'wpvulnerability-plugins', 'wpvulnerability-plugins-cache', 'wpvulnerability-plugins-vulnerable', 'wpvulnerability-core', 'wpvulnerability-core-cache', 'wpvulnerability-core-vulnerable', 'wpvulnerability-php', 'wpvulnerability-php-cache', 'wpvulnerability-php-vulnerable', 'wpvulnerability-apache', 'wpvulnerability-apache-cache', 'wpvulnerability-apache-vulnerable', 'wpvulnerability-nginx', 'wpvulnerability-nginx-cache', 'wpvulnerability-nginx-vulnerable', 'wpvulnerability-mariadb', 'wpvulnerability-mariadb-cache', 'wpvulnerability-mariadb-vulnerable', 'wpvulnerability-mysql', 'wpvulnerability-mysql-cache', 'wpvulnerability-mysql-vulnerable', 'wpvulnerability-imagemagick', 'wpvulnerability-imagemagick-cache', 'wpvulnerability-imagemagick-vulnerable', 'wpvulnerability-curl', 'wpvulnerability-curl-cache', 'wpvulnerability-curl-vulnerable', 'wpvulnerability-memcached', 'wpvulnerability-memcached-cache', 'wpvulnerability-memcached-vulnerable', 'wpvulnerability-redis', 'wpvulnerability-redis-cache', 'wpvulnerability-redis-vulnerable', 'wpvulnerability-sqlite', 'wpvulnerability-sqlite-cache', 'wpvulnerability-sqlite-vulnerable', 'wpvulnerability-statistics', 'wpvulnerability-statistics-cache', 'wpvulnerability_initialized', ); // Delete options based on the installation type. $delete_option_func = is_multisite() ? 'delete_site_option' : 'delete_option'; foreach ( $options as $option ) { $delete_option_func( $option ); } wpvulnerability_delete_transients(); // Unschedule and remove scheduled wp-cron jobs. $cron_jobs = array( 'wpvulnerability_notification', 'wpvulnerability_update_database', 'wpvulnerability_pull_db_data_event', 'wpvulnerability_cleanup_logs', ); foreach ( $cron_jobs as $job ) { $next_ts = wp_next_scheduled( $job ); if ( false !== $next_ts ) { wp_unschedule_event( $next_ts, $job ); } wp_clear_scheduled_hook( $job ); } } /** * Deletes all transients that start with 'wpvulnerability_'. * * @since 3.5.0 * * @return void */ function wpvulnerability_delete_transients() { global $wpdb; // Determine if the site is multisite. $is_multisite = is_multisite(); // Define the prefix according to whether it is multisite or not. $transient_prefix = $is_multisite ? '_site_transient_wpvulnerability_' : '_transient_wpvulnerability_'; // Prepare the LIKE pattern securely. $like_pattern = $wpdb->esc_like( $transient_prefix ) . '%'; // Try to get transients from cache first. $cache_key = 'wpvulnerability_transients_list'; $transients = wp_cache_get( $cache_key ); if ( false === $transients ) { // If cache is empty, query the database for matching transients. if ( $is_multisite ) { $transients = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( "SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s AND site_id = %d", $like_pattern, get_current_network_id() ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching } else { $transients = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $like_pattern ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching } // Store the result in cache for future use. wp_cache_set( $cache_key, $transients, '', HOUR_IN_SECONDS ); } // If no transients are found, exit early. if ( empty( $transients ) ) { return; } foreach ( $transients as $transient ) { if ( $is_multisite ) { // For multisite, delete using delete_site_transient. $transient_name = str_replace( '_site_transient_', '', $transient ); delete_site_transient( $transient_name ); } else { // For single sites, delete using delete_transient. $transient_name = str_replace( '_transient_', '', $transient ); delete_transient( $transient_name ); } } // Optionally clear the cache after deletion. wp_cache_delete( $cache_key ); } /** * Reset the plugin data to defaults and repopulate from the API. * * This routine mimics a full uninstall by deleting options, logs, and cron jobs, * then provisions default settings, reschedules events, and reloads data. * * @since 4.3.0 * * @return void */ function wpvulnerability_reset_plugin_data() { wpvulnerability_uninstall(); wpvulnerability_initialize_plugin_data( true ); if ( function_exists( 'wpvulnerability_schedule_core_events' ) ) { wpvulnerability_schedule_core_events(); } $config_raw = is_multisite() ? get_site_option( 'wpvulnerability-config', array() ) : get_option( 'wpvulnerability-config', array() ); $config = is_array( $config_raw ) ? $config_raw : array(); if ( function_exists( 'wpvulnerability_schedule_notification_event' ) ) { wpvulnerability_schedule_notification_event( $config ); } wpvulnerability_update_database_data(); } /** * Callback function to run when the plugin is uninstalled. * Deletes options and removes scheduled wp-cron jobs. * * @since 3.0.0 * * @return void */ function wpvulnerability_uninstall() { // Delete deprecated options. delete_option( 'wpvulnerability_settings' ); delete_option( 'wpvulnerability-data' ); if ( function_exists( 'delete_site_option' ) ) { delete_site_option( 'wpvulnerability_settings' ); delete_site_option( 'wpvulnerability-data' ); } // Options to delete for both single site and multisite. $options = array( 'wpvulnerability-themes', 'wpvulnerability-themes-cache', 'wpvulnerability-themes-vulnerable', 'wpvulnerability-themes-signature', 'wpvulnerability-plugins', 'wpvulnerability-plugins-cache', 'wpvulnerability-plugins-vulnerable', 'wpvulnerability-plugins-signature', 'wpvulnerability-plugins-data', 'wpvulnerability-plugins-cache-data', 'wpvulnerability-plugins-data-cache', 'wpvulnerability-core', 'wpvulnerability-core-cache', 'wpvulnerability-core-vulnerable', 'wpvulnerability-core-version', 'wpvulnerability-php', 'wpvulnerability-php-cache', 'wpvulnerability-php-vulnerable', 'wpvulnerability-apache', 'wpvulnerability-apache-cache', 'wpvulnerability-apache-vulnerable', 'wpvulnerability-nginx', 'wpvulnerability-nginx-cache', 'wpvulnerability-nginx-vulnerable', 'wpvulnerability-mariadb', 'wpvulnerability-mariadb-cache', 'wpvulnerability-mariadb-vulnerable', 'wpvulnerability-mysql', 'wpvulnerability-mysql-cache', 'wpvulnerability-mysql-vulnerable', 'wpvulnerability-imagemagick', 'wpvulnerability-imagemagick-cache', 'wpvulnerability-imagemagick-vulnerable', 'wpvulnerability-curl', 'wpvulnerability-curl-cache', 'wpvulnerability-curl-vulnerable', 'wpvulnerability-memcached', 'wpvulnerability-memcached-cache', 'wpvulnerability-memcached-vulnerable', 'wpvulnerability-redis', 'wpvulnerability-redis-cache', 'wpvulnerability-redis-vulnerable', 'wpvulnerability-sqlite', 'wpvulnerability-sqlite-cache', 'wpvulnerability-sqlite-vulnerable', 'wpvulnerability-statistics', 'wpvulnerability-statistics-cache', 'wpvulnerability_initialized', 'wpvulnerability-analyze', ); foreach ( $options as $option ) { delete_option( $option ); if ( function_exists( 'delete_site_option' ) ) { delete_site_option( $option ); } } // Delete all stored log entries in batches to avoid timeouts. $log_query_args = array( 'post_type' => 'wpvulnerability_log', 'fields' => 'ids', 'post_status' => 'any', 'posts_per_page' => 100, 'orderby' => 'ID', 'order' => 'ASC', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'suppress_filters' => false, ); while ( true ) { $log_ids = get_posts( $log_query_args ); if ( empty( $log_ids ) ) { break; } foreach ( $log_ids as $log_id ) { wp_delete_post( (int) $log_id, true ); } } // Delete config data. delete_option( 'wpvulnerability-config' ); if ( function_exists( 'delete_site_option' ) ) { delete_site_option( 'wpvulnerability-config' ); } wpvulnerability_delete_transients(); // Unschedule and remove scheduled wp-cron jobs. $cron_jobs = array( 'wpvulnerability_notification', 'wpvulnerability_update_database', 'wpvulnerability_pull_db_data_event', 'wpvulnerability_cleanup_logs', ); foreach ( $cron_jobs as $job ) { $next_ts = wp_next_scheduled( $job ); if ( false !== $next_ts ) { wp_unschedule_event( $next_ts, $job ); } wp_clear_scheduled_hook( $job ); } } /** * Filters and returns the WPVulnerability analysis setting for a given type. * * This function retrieves the WPVulnerability analysis settings, either from * the single site or the multisite network, depending on the WordPress setup. * It returns false if the specified type ('core', 'plugins', 'themes', * 'php', 'apache', 'nginx', 'mariadb', 'mysql') is set. If the type is not set or is invalid, it returns true. * * @since 3.3.0 * * @param string $type The type of analysis setting to retrieve ('core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql'). * * @return bool False if the specified type is set, true if not set or invalid. */ function wpvulnerability_analyze_filter( $type ) { // Retrieve the analysis settings based on the WordPress setup. $raw_analyze = is_multisite() ? get_site_option( 'wpvulnerability-analyze', array() ) : get_option( 'wpvulnerability-analyze', array() ); $wpvulnerability_analyze = is_array( $raw_analyze ) ? $raw_analyze : array(); // Define the valid types for analysis. $valid_types = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' ); if ( in_array( $type, $valid_types, true ) ) { $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $type ); if ( defined( $constant ) && constant( $constant ) ) { return false; } $type_val = $wpvulnerability_analyze[ $type ] ?? 0; return ! ( is_numeric( $type_val ) && (int) $type_val ); } return true; // Return true for invalid types. } /** * Clean the cache after an update. * * This function is triggered after a plugin or theme update to clean the cache * and refresh the vulnerability data. * * @since 2.0.0 * * @return void */ add_action( 'upgrader_process_complete', 'wpvulnerability_update_database_data', 10, 0 ); /** * Adds a notification count to the Plugins menu item in the WordPress admin if there are vulnerable plugins. * * This function retrieves the number of vulnerable plugins from the cache, either from a single site * or a multisite setup, and displays the count in the WordPress admin menu next to the Plugins menu item. * The count is shown with a gold background (#FFD700) and black text. * * @since 3.3.5 * * @return void */ function wpvulnerability_counter_plugins() { if ( ! wpvulnerability_analyze_filter( 'plugins' ) ) { return; // Skip if plugin analysis is disabled. } // Retrieve the number of vulnerable plugins from cache. $wpvulnerability_plugins_count = is_multisite() && is_network_admin() ? get_site_option( 'wpvulnerability-plugins-vulnerable' ) : get_option( 'wpvulnerability-plugins-vulnerable' ); // Decode the count from JSON, default to 0 if not set. $wpvulnerability_plugins_decoded = is_string( $wpvulnerability_plugins_count ) ? json_decode( $wpvulnerability_plugins_count ) : 0; $wpvulnerability_plugins_total = is_numeric( $wpvulnerability_plugins_decoded ) ? (int) $wpvulnerability_plugins_decoded : 0; if ( $wpvulnerability_plugins_total > 0 ) { global $menu; foreach ( $menu as $key => $value ) { if ( 'plugins.php' === $menu[ $key ][2] ) { $menu[ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_plugins_total ) . ''; // phpcs:ignore break; } } } } // Hook into the appropriate admin menu action based on the site type. if ( is_multisite() && is_network_admin() ) { add_action( 'network_admin_menu', 'wpvulnerability_counter_plugins' ); } elseif ( ! is_multisite() ) { add_action( 'admin_menu', 'wpvulnerability_counter_plugins' ); } /** * Adds a notification count to the Themes menu item in the WordPress admin if there are vulnerable themes. * * This function retrieves the number of vulnerable themes from the cache, either from a single site * or a multisite setup, and displays the count in the WordPress admin menu next to the Themes menu item. * The count is displayed with a gold background (#FFD700) and black text. * * @since 3.3.5 * * @return void */ function wpvulnerability_counter_themes() { if ( ! wpvulnerability_analyze_filter( 'themes' ) ) { return; // Skip if theme analysis is disabled. } // Retrieve the number of theme vulnerabilities from cache. $wpvulnerability_themes_count = ( is_multisite() && is_network_admin() ) ? get_site_option( 'wpvulnerability-themes-vulnerable' ) : get_option( 'wpvulnerability-themes-vulnerable' ); // Decode the count from JSON, default to 0 if not set. $wpvulnerability_themes_decoded = is_string( $wpvulnerability_themes_count ) ? json_decode( $wpvulnerability_themes_count ) : 0; $wpvulnerability_themes_total = is_numeric( $wpvulnerability_themes_decoded ) ? (int) $wpvulnerability_themes_decoded : 0; if ( $wpvulnerability_themes_total > 0 ) { // Check if we are in a multisite setup or not. if ( ! is_multisite() ) { global $submenu; // Check if the submenu for themes exists. if ( isset( $submenu['themes.php'] ) ) { foreach ( $submenu['themes.php'] as $key => $value ) { if ( 'themes.php' === $submenu['themes.php'][ $key ][2] ) { $submenu['themes.php'][ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_themes_total ) . ''; // phpcs:ignore break; } } } } elseif ( is_network_admin() ) { global $menu; foreach ( $menu as $key => $value ) { if ( 'themes.php' === $menu[ $key ][2] ) { $menu[ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_themes_total ) . ''; // phpcs:ignore break; } } } } } // Hook into the appropriate admin menu action based on the site type. if ( is_multisite() && is_network_admin() ) { add_action( 'network_admin_menu', 'wpvulnerability_counter_themes' ); } elseif ( ! is_multisite() ) { add_action( 'admin_menu', 'wpvulnerability_counter_themes' ); } /** * Adds a notification count to the Updates submenu item under Dashboard in the WordPress admin if there are core updates. * * This function checks for core updates and then displays the count in the WordPress admin submenu * next to the Updates menu item with a gold background (#FFD700) and black text. * * @since 3.3.5 * * @return void */ function wpvulnerability_counter_core() { if ( ! wpvulnerability_analyze_filter( 'core' ) ) { return; // Skip if core analysis is disabled. } // Retrieve the number of core vulnerabilities from cache. $wpvulnerability_core_count = is_multisite() && is_network_admin() ? get_site_option( 'wpvulnerability-core-vulnerable' ) : get_option( 'wpvulnerability-core-vulnerable' ); // Decode the count from JSON, default to 0 if not set. $wpvulnerability_core_decoded = is_string( $wpvulnerability_core_count ) ? json_decode( $wpvulnerability_core_count ) : 0; $wpvulnerability_core_total = is_numeric( $wpvulnerability_core_decoded ) ? (int) $wpvulnerability_core_decoded : 0; if ( $wpvulnerability_core_total > 0 ) { global $submenu; if ( isset( $submenu['index.php'] ) ) { foreach ( $submenu['index.php'] as $key => $value ) { if ( 'update-core.php' === $submenu['index.php'][ $key ][2] ) { $submenu['index.php'][ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_core_total ) . ''; // phpcs:ignore break; } } } } } // Hook into the appropriate admin menu action based on the site type. if ( is_multisite() && is_network_admin() ) { add_action( 'network_admin_menu', 'wpvulnerability_counter_core' ); } elseif ( ! is_multisite() ) { add_action( 'admin_menu', 'wpvulnerability_counter_core' ); }