HEX
Server: Apache
System: Linux vps-3158868-x.dattaweb.com 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
User: emerlux (1185)
PHP: 8.3.1
Disabled: system, shell, exec, system_exec, shell_exec, mysql_pconnect, passthru, popen, proc_open, proc_close, proc_nice, proc_terminate, proc_get_status, escapeshellarg, escapeshellcmd, eval
Upload Files
File: /home/emerlux/public_html/wp-content/plugins/wpvulnerability/wpvulnerability-plugins.php
<?php
/**
 * Plugin functions
 *
 * @package WPVulnerability
 *
 * @version 2.0.0
 */

defined( 'ABSPATH' ) || die( 'No script kiddies please!' );

/**
 * Enqueues the admin JavaScript for plugin update interactions.
 *
 * @since 4.1.0
 *
 * @param string $hook Current admin page hook.
 *
 * @return void
 */
function wpvulnerability_plugins_admin_enqueue_scripts( $hook ) {
	if ( 'plugins.php' !== $hook && 'plugins-network' !== $hook ) {
		return;
	}

	wp_enqueue_script(
		'wpvulnerability-admin-js',
		WPVULNERABILITY_PLUGIN_URL . 'assets/admin.js',
		array( 'jquery' ),
		WPVULNERABILITY_PLUGIN_VERSION,
		true
	);
}
add_action( 'admin_enqueue_scripts', 'wpvulnerability_plugins_admin_enqueue_scripts' );

/**
 * Adds a vulnerability notice under vulnerable plugins.
 *
 * This function retrieves the vulnerability data for the specified plugin from the WordPress options table
 * and displays a detailed notice below the plugin's row on the plugins management page in the WordPress admin area.
 * The notice includes information about the plugin's vulnerabilities, such as affected versions, severity, CVSS scores,
 * and links to sources.
 *
 * The function is applicable both in single-site and multisite installations. In a multisite setup, the notice
 * is displayed only in the network admin area or in the site admin area of individual sites.
 *
 * @since 2.0.0
 *
 * @param string $plugin_file Main plugin folder/file name.
 * @param array  $plugin_data Plugin data array containing information about the plugin.
 *
 * @return void
 */
function wpvulnerability_plugin_info_after( $plugin_file, $plugin_data ) {

	// Retrieve the vulnerabilities for all plugins from the options table and decode the JSON.
	if ( is_multisite() ) {
		$plugin_vulnerabilities = json_decode( get_site_option( 'wpvulnerability-plugins' ), true );
	} else {
		$plugin_vulnerabilities = json_decode( get_option( 'wpvulnerability-plugins' ), true );
	}

	if ( ( is_multisite() && is_network_admin() ) || ! is_multisite() ) {

		// Determine whether the plugin is active and add an appropriate CSS class to the table row.
		$tr_class = is_plugin_active( $plugin_file ) ? 'active' : '';

		// Generate the vulnerability notice message with the plugin name.
		$message = sprintf(
			/* translators: 1: Plugin name */
			__( '%1$s has a known vulnerability that may be affecting this version.', 'wpvulnerability' ),
			wp_kses( (string) $plugin_data['Name'], 'strip' )
		);

		// Begin generating the table row HTML markup with appropriate CSS classes and the vulnerability notice message.
		$information  = '<tr class="wpvulnerability ' . esc_attr( $tr_class ) . '">';
		$information .= '<td colspan="4">';
		$information .= '<p class="text-red"><img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/logo16.png" style="height: 16px; vertical-align: text-top; width: 16px;" alt="" title="WPVulnerability"> <strong>' . $message . '</strong>';
		$information .= '</p>';
		$information .= '<table>';

		// Loop through all vulnerabilities for the current plugin and add their details to the table row HTML markup.
		$vulnerabilities = isset( $plugin_vulnerabilities[ $plugin_file ]['vulnerabilities'] ) ? $plugin_vulnerabilities[ $plugin_file ]['vulnerabilities'] : array();

		foreach ( $vulnerabilities as $vulnerability ) {

			$what = array();
			if ( isset( $vulnerability['impact']['cwe'] ) ) {
				foreach ( $vulnerability['impact']['cwe'] as $vulnerability_cwe ) {
					$what[] = '<div><b>' . wp_kses( (string) $vulnerability_cwe['name'], 'strip' ) . '</b></div><div><i>' . wp_kses_post( (string) $vulnerability_cwe['description'] ) . '</i></div>';
				}
			}

			$sources = array();
			if ( isset( $vulnerability['source'] ) ) {
				foreach ( $vulnerability['source'] as $vulnerability_source ) {
					$sources[] = '<a href="' . esc_url_raw( (string) $vulnerability_source['link'], 'strip' ) . '" target="_blank" rel="external nofollow noopener noreferrer">[+]</a>&nbsp;' . wp_kses( (string) $vulnerability_source['name'], 'strip' );
				}
			}
			if ( ! empty( $sources ) ) {
				$source = '<div style="padding-bottom: 5px;">' . implode( '<br>', $sources ) . '</div>';
			}

			$score    = isset( $vulnerability['impact']['cvss']['score'] ) ? number_format( (float) $vulnerability['impact']['cvss']['score'], 1, '.', '' ) : null;
			$severity = isset( $vulnerability['impact']['cvss']['severity'] ) ? wpvulnerability_severity( $vulnerability['impact']['cvss']['severity'] ) : null;

			$information .= '<tr>';
			$information .= '<td style="max-width: 256px; min-width: 96px;"><b>' . wp_kses( (string) $vulnerability['versions'], 'strip' ) . '</b></td>';
			$information .= '<td>';
			if ( (int) $vulnerability['closed'] || (int) $vulnerability['unfixed'] ) {
				$information .= '<div style="padding-bottom: 5px;">';
				if ( (int) $vulnerability['closed'] ) {
					$information .= '<div class="text-red">' . __( 'This plugin is closed. Please replace it with another.', 'wpvulnerability' ) . '</div>';
				}
				if ( (int) $vulnerability['unfixed'] ) {
					$information .= '<div class="text-red">' . __( 'This vulnerability appears to be unpatched. Stay tuned for upcoming plugin updates.', 'wpvulnerability' ) . '</div>';
				}
				$information .= '</div>';
			}
			if ( ! empty( $what ) ) {
				$information .= '<div style="padding-bottom: 5px;">';
				foreach ( $what as $w ) {
					$information .= $w;
				}
				$information .= '</div>';
			}
			if ( ! is_null( $score ) || ! is_null( $severity ) ) {
				$information .= '<div style="padding-bottom: 5px;">';
				if ( ! is_null( $score ) ) {
					$information .= '<div>' . __( 'Global score: ', 'wpvulnerability' ) . $score . ' / 10</div>';
				}
				if ( ! is_null( $severity ) ) {
					$information .= '<div>' . __( 'Severity: ', 'wpvulnerability' ) . $severity . '</div>';
				}
				$information .= '</div>';
			}
			$information .= wp_kses( (string) $source, 'post' );
			$information .= '</td>';
			$information .= '</tr>';
		}

		$information .= '</table>';
		$information .= '</td>';
		$information .= '</tr>';

		echo $information; // phpcs:ignore
	}
}

/**
 * Retrieves vulnerabilities for a given plugin and updates its data.
 *
 * @since 2.0.0
 *
 * @param array  $plugin_data The plugin data array.
 * @param string $file_path The path to the plugin file.
 *
 * @return array The updated plugin data array.
 */
function wpvulnerability_get_fresh_plugin_vulnerabilities( $plugin_data, $file_path ) {

	$plugin_slug = null;

	// Extract the folder name from the file path.
	$folder_name = explode( '/', $file_path );

	// If a folder name is found, use it as the plugin slug.
	if ( isset( $folder_name[0] ) ) {
		$plugin_slug = wp_kses( trim( (string) $folder_name[0] ), 'strip' );
	}
	unset( $folder_name );

	// If the plugin slug is still null, use the TextDomain key from the plugin data.
	if ( is_null( $plugin_slug ) && isset( $plugin_data['TextDomain'] ) ) {
		$plugin_slug = wp_kses( (string) $plugin_data['TextDomain'], 'strip' );
	}

	// Get the plugin version from the plugin data.
	$plugin_version = isset( $plugin_data['Version'] ) ? wp_kses( (string) $plugin_data['Version'], 'strip' ) : '';

	// Initialize vulnerability-related fields.
	$plugin_data['vulnerabilities'] = null;
	$plugin_data['vulnerable']      = 0;

	// Retrieve vulnerabilities for the plugin using its slug and version.
	if ( ! empty( $plugin_slug ) ) {

		$plugin_api_response = wpvulnerability_get_plugin( $plugin_slug, $plugin_version, 0, 0 );

		// If vulnerabilities are found, update the plugin data accordingly.
		if ( ! empty( $plugin_api_response ) ) {

			$plugin_data['slug']            = $plugin_slug;
			$plugin_data['vulnerabilities'] = $plugin_api_response;
			$plugin_data['vulnerable']      = 1;

		}
	}

	return $plugin_data;
}

/**
 * Retrieves updated data for a specified plugin, potentially including vulnerability information.
 *
 * @since 3.1.0
 *
 * @param array  $plugin_data The original plugin data array, expected to contain keys like 'TextDomain' and 'Version'.
 * @param string $file_path   The file path of the plugin, used to determine the plugin's slug if 'TextDomain' is not specified in `$plugin_data`.
 *
 * @return array|null Updated plugin data array with fresh information or null if the plugin slug cannot be determined or no updated information is available.
 */
function wpvulnerability_get_fresh_plugin_data( $plugin_data, $file_path ) {

	$plugin_slug = null;

	// Extract the folder name from the file path.
	$folder_name = explode( '/', $file_path );

	// If a folder name is found, use it as the plugin slug.
	if ( isset( $folder_name[0] ) ) {
		$plugin_slug = wp_kses( trim( (string) $folder_name[0] ), 'strip' );
	}
	unset( $folder_name );

	// If the plugin slug is still null, use the TextDomain key from the plugin data if it exists.
	if ( is_null( $plugin_slug ) && isset( $plugin_data['TextDomain'] ) ) {
		$plugin_slug = wp_kses( (string) $plugin_data['TextDomain'], 'strip' );
	}

	// Get the plugin version from the plugin data if it exists.
	$plugin_version = isset( $plugin_data['Version'] ) ? wp_kses( (string) $plugin_data['Version'], 'strip' ) : '';

	// Retrieve vulnerabilities for the plugin using its slug and version.
	if ( ! empty( $plugin_slug ) ) {

		$plugin_api_response = wpvulnerability_get_plugin( $plugin_slug, $plugin_version, 1, 0 );

		// If vulnerabilities are found, return the updated plugin data.
		if ( ! empty( $plugin_api_response ) ) {
			return $plugin_api_response;
		}
	}

	return null; // Return null if no valid data is found.
}

/**
 * Get Installed Plugins
 * Retrieves the list of installed plugins, checks for vulnerabilities in each of them, caches the data, and sends an email notification if vulnerabilities are detected.
 *
 * @since 2.0.0
 *
 * @return string JSON-encoded array of plugin data with vulnerabilities and vulnerable status.
 */
function wpvulnerability_plugin_get_installed() {

	$wpvulnerability_plugins_vulnerable = 0;

	// Ensure the get_plugins() function is available.
	if ( ! function_exists( 'get_plugins' ) ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
	}

	// Retrieve the list of installed plugins.
	$plugins = get_plugins();

	// Iterate through each plugin and check for vulnerabilities.
	foreach ( $plugins as $file_path => $plugin_data ) {

		$plugins[ $file_path ] = wpvulnerability_get_fresh_plugin_vulnerabilities( $plugin_data, $file_path );

		// Increment the vulnerable plugin counter if vulnerabilities are found.
		if ( isset( $plugins[ $file_path ]['vulnerable'] ) && (int) $plugins[ $file_path ]['vulnerable'] ) {
			++$wpvulnerability_plugins_vulnerable;
		}
	}

	// Update site options for multisite installations.
	if ( is_multisite() ) {
				update_site_option( 'wpvulnerability-plugins', wp_json_encode( $plugins ) );
				update_site_option( 'wpvulnerability-plugins-vulnerable', wp_json_encode( number_format( $wpvulnerability_plugins_vulnerable, 0, '.', '' ) ) );
				update_site_option( 'wpvulnerability-plugins-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
	} else {
		// Update options for single site installations.
				update_option( 'wpvulnerability-plugins', wp_json_encode( $plugins ) );
				update_option( 'wpvulnerability-plugins-vulnerable', wp_json_encode( number_format( $wpvulnerability_plugins_vulnerable, 0, '.', '' ) ) );
				update_option( 'wpvulnerability-plugins-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
	}

	// Return the JSON-encoded array of plugin data.
	return wp_json_encode( $plugins );
}

/**
 * Retrieves and caches data for all installed plugins, optionally refreshing the cache.
 *
 * @since 3.1.0
 *
 * @param bool $clean Optional. Whether to force a refresh of the plugin data cache. Default false.
 *
 * @return string JSON-encoded array of updated plugin data. Each plugin is represented by its file path as the key and its updated data as the value.
 */
function wpvulnerability_plugin_get_data( $clean = false ) {

	if ( is_multisite() ) {
		// Get the cached plugin data and decode it.
		$plugin_data_cache = json_decode( get_site_option( 'wpvulnerability-plugins-cache-data' ) );
		$plugin_data       = json_decode( get_site_option( 'wpvulnerability-plugins-data' ), true );
	} else {
		// Get the cached plugin data and decode it.
		$plugin_data_cache = json_decode( get_option( 'wpvulnerability-plugins-cache-data' ) );
		$plugin_data       = json_decode( get_option( 'wpvulnerability-plugins-data' ), true );
	}

	// Refresh the cache if it is stale, empty, or a forced refresh is requested.
	if ( empty( $plugin_data_cache ) || empty( $plugin_data ) || $clean || $plugin_data_cache < time() ) {
		// Ensure the get_plugins() function is available.
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		// Retrieve the list of installed plugins.
		$plugins     = get_plugins();
		$pluginsdata = array();

		// Iterate through each plugin and get fresh data.
		foreach ( $plugins as $file_path => $plugin_data ) {
			$pluginsdata[ $file_path ] = wpvulnerability_get_fresh_plugin_data( $plugin_data, $file_path );
		}

		// Update site options for multisite installations.
		if ( is_multisite() ) {
						update_site_option( 'wpvulnerability-plugins-data', wp_json_encode( $pluginsdata ) );
						update_site_option( 'wpvulnerability-plugins-cache-data', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
						update_site_option( 'wpvulnerability-plugins-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
		} else {
			// Update options for single site installations.
						update_option( 'wpvulnerability-plugins-data', wp_json_encode( $pluginsdata ) );
						update_option( 'wpvulnerability-plugins-cache-data', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
						update_option( 'wpvulnerability-plugins-cache', wp_json_encode( number_format( time() + ( 3600 * wpvulnerability_cache_hours() ), 0, '.', '' ) ) );
		}
	}

	// Return the JSON-encoded array of plugin data.
	return wp_json_encode( $pluginsdata );
}

/**
 * Get the cached plugin vulnerabilities or update the cache if it's stale or missing.
 *
 * @since 2.0.0
 *
 * @return array Array of installed plugins with their vulnerabilities.
 */
function wpvulnerability_plugin_get_vulnerabilities() {

	if ( is_multisite() ) {
		// Get the cached plugin data and decode it.
		$plugin_data_cache = json_decode( get_site_option( 'wpvulnerability-plugins-cache' ), true );
		// Get the installed plugin data and decode it.
		$plugin_data = json_decode( get_site_option( 'wpvulnerability-plugins' ), true );
	} else {
		// Get the cached plugin data and decode it.
		$plugin_data_cache = json_decode( get_option( 'wpvulnerability-plugins-cache' ), true );
		// Get the installed plugin data and decode it.
		$plugin_data = json_decode( get_option( 'wpvulnerability-plugins' ), true );
	}

	// If the cache is stale or the plugin data is empty, update the cache.
	if ( ( isset( $plugin_data_cache ) && $plugin_data_cache < time() ) || empty( $plugin_data ) ) {
		// Get the installed plugin data and update the cache.
		$plugin_data = json_decode( wpvulnerability_plugin_get_installed(), true );
	}

	// Return the plugin data with vulnerabilities.
	return $plugin_data;
}

/**
 * Update the installed plugins cache and remove any old cache data.
 *
 * @since 2.0.0
 *
 * @return void
 */
function wpvulnerability_plugin_get_vulnerabilities_clean() {
	wpvulnerability_clear_cache( 'plugins' );
	wpvulnerability_plugin_get_installed();
	wpvulnerability_plugin_get_data( true );
}

/**
 * Displays information in the 'Last Updated' column for each plugin in the plugins list table.
 *
 * This function is triggered for each row in the plugins list table when the 'Last Updated' column is rendered.
 * It retrieves the last update date from stored plugin data, compares it against the current date to highlight
 * plugins not updated in over a year or those marked as closed, and displays this information.
 *
 * @since 3.1.0 Introduced.
 *
 * @param string $column_name The name of the current column being rendered.
 * @param string $plugin_file Path to the plugin file, relative to the plugins directory.
 * @param array  $plugin_data Array of plugin data, such as the plugin's name, version, and description.
 *
 * @return void Outputs the last updated information directly to the browser, including any warnings for plugins
 *              not updated in over a year or marked as closed.
 */
function wpvulnerability_plugin_show_lastupdated( $column_name, $plugin_file, $plugin_data ) {

	$now  = time();
	$year = strtotime( '-1 year', $now );

	if ( 'last_updated' === $column_name && $plugin_file ) {

		$plugin_slug = null;

		// Extract the plugin slug from the file path.
		$folder_name = explode( '/', $plugin_file );

		// If a folder name is found, use it as the plugin slug.
		if ( isset( $folder_name[0] ) ) {
			$plugin_slug = wp_kses( trim( (string) $folder_name[0] ), 'strip' );
		}
		unset( $folder_name );

		// If the TextDomain key is empty, extract it from the plugin data.
		if ( is_null( $plugin_slug ) ) {
			$plugin_slug = wp_kses( (string) $plugin_data['TextDomain'], 'strip' );
		}

		if ( isset( $plugin_slug ) ) {

			// Retrieve the vulnerabilities for all plugins from the options table and decode the JSON.
			if ( is_multisite() ) {
				$plugins_data = json_decode( get_site_option( 'wpvulnerability-plugins-data' ), true );
			} else {
				$plugins_data = json_decode( get_option( 'wpvulnerability-plugins-data' ), true );
			}

			// Get the plugin data from the stored data.
			if ( isset( $plugins_data[ $plugin_file ] ) ) {
				$plugin_data = $plugins_data[ $plugin_file ];

				if ( isset( $plugin_data['latest'] ) && (int) $plugin_data['latest'] > 0 ) {

					$timestamp   = (int) $plugin_data['latest'];
					$date_format = get_option( 'date_format' );
					if ( function_exists( 'wp_date' ) ) {
						$plugin_data_updated = wp_date( $date_format, $timestamp );
					} else {
						$plugin_data_updated = gmdate( $date_format, $timestamp );
					}
					$plugin_data_ago = human_time_diff( (int) $plugin_data['latest'] );

					$warning_date   = (int) $plugin_data['latest'] < $year;
					$warning_closed = isset( $plugin_data['closed'] ) && (int) $plugin_data['closed'];

					echo '<p>' . wp_kses( (string) $plugin_data_updated, 'strip' ) . ' (' . wp_kses( (string) $plugin_data_ago, 'strip' ) . ')</p>';

					if ( $warning_date ) {
						echo '<p><strong>⚠️ ';
						esc_html_e( 'It hasn\'t been updated in over a year.', 'wpvulnerability' );
						echo '</strong></p>';
					}

					if ( $warning_closed ) {
						echo '<p><strong>⚠️ ';
						esc_html_e( 'It may no longer be available (closed?).', 'wpvulnerability' );
						echo '</strong></p>';
					}
				} else {
					echo '<p></p>';
				}
			}
		}
	}
}

/**
 * Adds a 'Last Updated' column to the plugins table list in the WordPress admin area.
 *
 * This function iterates over the existing columns in the plugins table and inserts a new column titled 'Last Updated'
 * just before the 'auto-updates' column if it exists. If the 'auto-updates' column is not found, the 'Last Updated'
 * column is appended at the end. The function is typically hooked to the 'manage_plugins_columns' filter in WordPress
 * to modify the columns of the plugins table.
 *
 * @since 3.1.0 Introduced.
 *
 * @param array $columns An associative array of column names and titles for the plugins table.
 *
 * @return array An associative array containing the modified list of columns, including the new 'Last Updated' column.
 */
function wpvulnerability_plugin_add_lastupdated_column( $columns ) {

	$toadd       = true;
	$new_columns = array();

	// Loop through each existing column and add it to the new columns array.
	foreach ( $columns as $key => $title ) {

		// Add the existing column to the new columns array.
		$new_columns[ $key ] = $title;

		// Insert your custom column before the 'auto-updates' column.
		if ( 'description' === $key && $toadd ) {
			$new_columns['last_updated'] = __( 'Last updated on', 'wpvulnerability' );
			$toadd                       = false;
		}
	}

	// If 'auto-updates' column is not found, add 'last_updated' column at the end.
	if ( $toadd ) {
		$new_columns['last_updated'] = __( 'Last updated on', 'wpvulnerability' );
	}

	// Return the modified columns array.
	return $new_columns;
}

/**
 * Admin Head
 * Adds vulnerability information after the plugin row and notices on the plugin page based on the installed plugins cache.
 *
 * @since 2.0.0
 *
 * @return void
 */
function wpvulnerability_plugin_page() {

	// Check if the current page is the plugins page.
	global $pagenow;

	if ( wpvulnerability_analyze_filter( 'plugins' ) && 'plugins.php' === $pagenow && wpvulnerability_capabilities() ) {

		// Get the vulnerabilities for the installed plugins.
		$plugins = wpvulnerability_plugin_get_vulnerabilities();

		// Loop through the plugins and add vulnerability information after the plugin row for vulnerable plugins.
		foreach ( $plugins as $file_path => $plugin_data ) {

			if ( isset( $plugin_data['vulnerable'] ) && 1 === (int) $plugin_data['vulnerable'] ) {

				add_action( 'after_plugin_row_' . $file_path, 'wpvulnerability_plugin_info_after', 10, 3 );

			}
		}

		// Add 'Last Updated' column to the plugins table based on user capabilities.
		if ( is_multisite() ) {

			add_filter( 'manage_plugins-network_columns', 'wpvulnerability_plugin_add_lastupdated_column' );

		} else {

			add_filter( 'manage_plugins_columns', 'wpvulnerability_plugin_add_lastupdated_column' );

		}

		add_filter( 'manage_plugins_custom_column', 'wpvulnerability_plugin_show_lastupdated', 10, 3 );

	}
}
// Add notices for vulnerable plugins on the plugin page.
add_action( 'admin_head', 'wpvulnerability_plugin_page' );

/**
 * Filters the plugins list to show only vulnerable plugins when the "Vulnerable" tab is selected.
 *
 * This function hooks into the WordPress plugins listing to filter the displayed plugins based on their
 * vulnerability status. When the "Vulnerable" tab is selected (identified by the `plugin_status=vulnerable`
 * query parameter), it filters the plugins list to include only those plugins with known vulnerabilities.
 *
 * The function retrieves the vulnerabilities for all plugins from the WordPress options table and compares
 * them against the active list of plugins. Plugins without vulnerabilities are removed from the list, leaving
 * only those that are considered vulnerable.
 *
 * @since 3.3.5
 *
 * @return void
 */
function wpvulnerability_plugins_filter() {
	if ( isset( $_GET['plugin_status'] ) && 'vulnerable' === $_GET['plugin_status'] ) { // phpcs:ignore

		global $wp_list_table;

		// Retrieve the vulnerabilities for all plugins from the options table and decode the JSON.
		if ( is_multisite() ) {
			$plugin_vulnerabilities        = json_decode( get_site_option( 'wpvulnerability-plugins' ), true );
			$wpvulnerability_plugins_count = get_site_option( 'wpvulnerability-plugins-vulnerable' );
		} else {
			$plugin_vulnerabilities        = json_decode( get_option( 'wpvulnerability-plugins' ), true );
			$wpvulnerability_plugins_count = get_option( 'wpvulnerability-plugins-vulnerable' );
		}

		foreach ( $wp_list_table->items as $plugin_file => $plugin_data ) {
			if ( ! isset( $plugin_vulnerabilities[ $plugin_file ]['vulnerabilities'] ) ||
				! count( $plugin_vulnerabilities[ $plugin_file ]['vulnerabilities'] ) ) {

				unset( $wp_list_table->items[ $plugin_file ] );
			}
		}
	}
}
add_action( 'pre_current_active_plugins', 'wpvulnerability_plugins_filter' );

/**
 * Adds a "Vulnerable" tab to the WordPress plugins page that displays the count of vulnerable plugins.
 *
 * This function checks the cache for the number of vulnerable plugins and adds a new tab to the plugins
 * management page in the WordPress admin area. The tab displays the count of vulnerable plugins and highlights it
 * if it is currently active.
 *
 * @since 3.3.5
 *
 * @param array $views An array of existing plugin views (tabs) in the WordPress admin plugins page.
 *
 * @return array The modified array of views including the "Vulnerable" tab.
 */
function wpvulnerability_plugins_view( $views ) {

	if ( ! wpvulnerability_analyze_filter( 'plugins' ) ) {
		return $views;
	}

	// Retrieve the number of plugins vulnerabilities from cache.
	$wpvulnerability_plugins_count = is_multisite()
		? get_site_option( 'wpvulnerability-plugins-vulnerable' )
		: get_option( 'wpvulnerability-plugins-vulnerable' );

	$wpvulnerability_plugins_total = 0;
	if ( $wpvulnerability_plugins_count ) {
		$wpvulnerability_plugins_total = json_decode( $wpvulnerability_plugins_count );
	}

	$current_class = ( isset( $_GET['plugin_status'] ) && 'vulnerable' === $_GET['plugin_status'] ) ? ' class="current"' : ''; // phpcs:ignore

	$url = is_multisite()
		? network_admin_url( 'plugins.php?plugin_status=vulnerable' )
		: admin_url( 'plugins.php?plugin_status=vulnerable' );

	$views['vulnerable'] = sprintf(
		'<a href="%s"%s>%s</a>',
		$url,
		$current_class,
		// translators: the number of vulnerabilities.
		sprintf( __( 'Vulnerabilities (%d)', 'wpvulnerability' ), $wpvulnerability_plugins_total )
	);

	return $views;
}

/**
 * Adds a custom filter to the plugins page in the WordPress admin to display a tab for vulnerable plugins.
 *
 * This function hooks into the 'views_plugins' filter to add a custom tab or view for displaying vulnerable plugins
 * on the plugins management page in the WordPress admin area. The tab is added in both single-site and multisite
 * installations, but in a multisite setup, it is only added to the network admin area.
 *
 * @since 3.3.5
 *
 * @return void
 */
function wpvulnerability_plugins_add_tab() {

	if ( is_multisite() ) {
		if ( is_network_admin() ) {
			add_filter( 'views_plugins-network', 'wpvulnerability_plugins_view' );
		}
	} else {
		add_filter( 'views_plugins', 'wpvulnerability_plugins_view' );
	}
}
add_action( 'admin_head', 'wpvulnerability_plugins_add_tab' );