invalid_plugins)) { return $this->invalid_plugins; } // Initialize empty array $invalid_plugins = array(); // Handle multisite network admin context if (is_multisite() && is_network_admin()) { $active_plugins = get_site_option('active_sitewide_plugins', array()); // Network active plugins are stored as key => timestamp $active_plugins = array_keys($active_plugins); } else { // Single site or non-network admin context $active_plugins = get_option('active_plugins', array()); } // Check each active plugin foreach ($active_plugins as $plugin_file) { $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; if (!file_exists($plugin_path)) { $invalid_plugins[] = $plugin_file; } } // Cache the result $this->invalid_plugins = $invalid_plugins; return $invalid_plugins; } /** * Check if the current page is the plugins page. * * @return bool True if on the plugins page, false otherwise. */ public function is_plugins_page() { global $pagenow; return is_admin() && 'plugins.php' === $pagenow; } /** * Find and add invalid plugin references to the plugins list. * * Filters the list of plugins displayed on the plugins page to include * entries for active plugins whose files are missing. * * @param array $plugins An array of plugin data. * @return array The potentially modified array of plugin data. */ public function add_missing_plugins_references($plugins) { // Only run on the plugins page if (!$this->is_plugins_page()) { return $plugins; } // Get active plugins that don't exist $invalid_plugins = $this->get_invalid_plugins(); // Add each invalid plugin to the plugin list foreach ($invalid_plugins as $plugin_path) { if (!isset($plugins[$plugin_path])) { $plugin_name = basename($plugin_path); $plugin_slug = dirname($plugin_path); if ('.' === $plugin_slug) { $plugin_slug = basename($plugin_path, '.php'); } // Create a basic plugin data array $plugins[$plugin_path] = array( 'Name' => $plugin_name . ' (File Missing)', /* translators: %s: Path to wp-content/plugins */ 'Description' => sprintf( __('This plugin is still marked as "Active" in your database — but its folder and files can\'t be found in %s. Click "Remove Notice" to permanently remove it from your active plugins list and eliminate the error notice.', 'wp-fix-plugin-does-not-exist-notices'), '/wp-content/plugins/' ), 'Version' => FPDEN_VERSION, // Use our plugin version instead of 'N/A' 'Author' => 'Marcus Quinn & WPALLSTARS', 'PluginURI' => 'https://www.wpallstars.com', 'AuthorURI' => 'https://www.wpallstars.com', 'Title' => $plugin_name . ' (' . __('Missing', 'wp-fix-plugin-does-not-exist-notices') . ')', 'AuthorName' => 'Marcus Quinn & WPALLSTARS', ); // Add the data needed for the "View details" link $plugins[$plugin_path]['slug'] = $plugin_slug; $plugins[$plugin_path]['plugin'] = $plugin_path; $plugins[$plugin_path]['type'] = 'plugin'; // Add Git Updater fields $plugins[$plugin_path]['GitHub Plugin URI'] = 'wpallstars/wp-fix-plugin-does-not-exist-notices'; $plugins[$plugin_path]['GitHub Branch'] = 'main'; $plugins[$plugin_path]['TextDomain'] = 'wp-fix-plugin-does-not-exist-notices'; } } return $plugins; } /** * Add the Remove Notice action link to invalid plugins. * * Filters the action links displayed for each plugin on the plugins page. * Adds a "Remove Notice" link for plugins identified as missing. * * @param array $actions An array of plugin action links. * @param string $plugin_file Path to the plugin file relative to the plugins directory. * @param array $plugin_data An array of plugin data. * @param string $context The plugin context (e.g., 'all', 'active', 'inactive'). * @return array The potentially modified array of plugin action links. */ public function add_remove_reference_action($actions, $plugin_file, $plugin_data, $context) { // Only run on the plugins page if (!$this->is_plugins_page()) { return $actions; } // Get our list of invalid plugins $invalid_plugins = $this->get_invalid_plugins(); // Check if this plugin file is in our list of invalid plugins if (in_array($plugin_file, $invalid_plugins, true)) { // Clear existing actions like "Activate", "Deactivate", "Edit" $actions = array(); // Add our custom action $nonce = wp_create_nonce('remove_plugin_reference_' . $plugin_file); $remove_url = admin_url('plugins.php?action=remove_reference&plugin=' . urlencode($plugin_file) . '&_wpnonce=' . $nonce); /* translators: %s: Plugin file path */ $aria_label = sprintf(__('Remove reference to missing plugin %s', 'wp-fix-plugin-does-not-exist-notices'), esc_attr($plugin_file)); $actions['remove_reference'] = '' . esc_html__('Remove Notice', 'wp-fix-plugin-does-not-exist-notices') . ''; } return $actions; } /** * Handle the remove reference action triggered by the link. * * Checks for the correct action, verifies nonce and permissions, * calls the removal function, and redirects back to the plugins page. * * @return void */ public function handle_remove_reference() { // Check if our specific action is being performed if (!isset($_GET['action']) || 'remove_reference' !== $_GET['action'] || !isset($_GET['plugin'])) { return; } // Verify user permissions if (!current_user_can('activate_plugins')) { wp_die(esc_html__('You do not have sufficient permissions to perform this action.', 'wp-fix-plugin-does-not-exist-notices')); } // Sanitize and get the plugin file path $plugin_file = isset($_GET['plugin']) ? sanitize_text_field(wp_unslash($_GET['plugin'])) : ''; if (empty($plugin_file)) { wp_die(esc_html__('Invalid plugin specified.', 'wp-fix-plugin-does-not-exist-notices')); } // Verify nonce for security check_admin_referer('remove_plugin_reference_' . $plugin_file); // Attempt to remove the plugin reference $success = $this->remove_plugin_reference($plugin_file); // Prepare redirect URL with feedback query args $redirect_url = admin_url('plugins.php'); $redirect_url = add_query_arg($success ? 'reference_removed' : 'reference_removal_failed', '1', $redirect_url); // Redirect and exit wp_safe_redirect($redirect_url); exit; } /** * Remove a plugin reference from the active plugins list in the database. * * Handles both single site and multisite network activated plugins. * * @param string $plugin_file The plugin file path to remove. * @return bool True on success, false on failure or if the plugin wasn't found. */ public function remove_plugin_reference($plugin_file) { $success = false; // Ensure plugin file path is provided if (empty($plugin_file)) { return false; } // Handle multisite network admin context if (is_multisite() && is_network_admin()) { $active_plugins = get_site_option('active_sitewide_plugins', array()); // Network active plugins are stored as key => timestamp if (isset($active_plugins[$plugin_file])) { unset($active_plugins[$plugin_file]); $success = update_site_option('active_sitewide_plugins', $active_plugins); } } else { // Handle single site or non-network admin context $active_plugins = get_option('active_plugins', array()); // Single site active plugins are stored as an indexed array $key = array_search($plugin_file, $active_plugins, true); // Use strict comparison if (false !== $key) { unset($active_plugins[$key]); // Re-index the array numerically $active_plugins = array_values($active_plugins); $success = update_option('active_plugins', $active_plugins); } } return $success; } /** * Display admin notices on the plugins page. * * Shows feedback messages after attempting to remove a reference. * The main informational notice is handled by JavaScript to position it * directly below the WordPress error message. * * @return void */ public function admin_notices() { // Only run on the plugins page if (!$this->is_plugins_page()) { return; } // Check for feedback messages from the remove action if (isset($_GET['reference_removed']) && '1' === $_GET['reference_removed']) { ?>

slug)) { return $result; } // Get our list of invalid plugins $invalid_plugins = $this->get_invalid_plugins(); // Check if this is our plugin or a missing plugin $our_plugin = in_array($args->slug, array('wp-fix-plugin-does-not-exist-notices', 'fix-plugin-does-not-exist-notices'), true); $is_missing_plugin = $this->is_missing_plugin($args->slug, $invalid_plugins); // Only modify the result if this is our plugin or a missing plugin if ($our_plugin || $is_missing_plugin) { // Create a new result object $new_result = new \stdClass(); // Set all the properties we need $new_result->name = $our_plugin ? 'Fix \'Plugin file does not exist\' Notices' : (isset($result->name) ? $result->name : $args->slug); $new_result->slug = $args->slug; $new_result->version = FPDEN_VERSION; $new_result->author = 'Marcus Quinn & WPALLSTARS'; $new_result->author_profile = 'https://www.wpallstars.com'; $new_result->requires = '5.0'; $new_result->tested = '6.7.2'; // Updated to match readme.txt $new_result->requires_php = '7.0'; $new_result->last_updated = date('Y-m-d H:i:s'); // Add a cache buster timestamp $new_result->cache_buster = time(); // Get full readme content for our plugin $readme_file = FPDEN_PLUGIN_DIR . 'readme.txt'; $readme_content = ''; $description = ''; $changelog = ''; $faq = ''; $installation = ''; $screenshots = ''; if (file_exists($readme_file) && $our_plugin) { $readme_content = file_get_contents($readme_file); // Extract description if (preg_match('/== Description ==(.+?)(?:==|$)/s', $readme_content, $matches)) { $description = trim($matches[1]); } // Extract changelog if (preg_match('/== Changelog ==(.+?)(?:==|$)/s', $readme_content, $matches)) { $changelog = trim($matches[1]); } // Extract FAQ if (preg_match('/== Frequently Asked Questions ==(.+?)(?:==|$)/s', $readme_content, $matches)) { $faq = trim($matches[1]); } // Extract installation if (preg_match('/== Installation ==(.+?)(?:==|$)/s', $readme_content, $matches)) { $installation = trim($matches[1]); } // Extract screenshots if (preg_match('/== Screenshots ==(.+?)(?:==|$)/s', $readme_content, $matches)) { $screenshots = trim($matches[1]); } } else { // Fallback content if readme.txt doesn't exist or for missing plugins $changelog = '

' . FPDEN_VERSION . '

'; } // Set description based on whether this is our plugin or a missing plugin if ($our_plugin) { $description = !empty($description) ? wpautop($description) : 'Adds missing plugins to your plugins list with a "Remove Notice" action link, allowing you to safely clean up invalid plugin references.'; } else { $description = sprintf( __('This plugin is still marked as "Active" in your database — but its folder and files can\'t be found in %s. Use the "Remove Notice" link on the plugins page to permanently remove it from your active plugins list and eliminate the error notice.', 'wp-fix-plugin-does-not-exist-notices'), '/wp-content/plugins/' ); } // Prepare sections $new_result->sections = array( 'description' => $description, 'changelog' => !empty($changelog) ? wpautop($changelog) : $changelog, 'faq' => !empty($faq) ? wpautop($faq) : '

Is it safe to remove plugin references?

Yes, this plugin only removes entries from the WordPress active_plugins option, which is safe to modify when a plugin no longer exists.

', ); // Add installation section if available if (!empty($installation)) { $new_result->sections['installation'] = wpautop($installation); } // Add screenshots section if available if (!empty($screenshots)) { $new_result->sections['screenshots'] = wpautop($screenshots); } // Add contributors information $new_result->contributors = array( 'marcusquinn' => array( 'profile' => 'https://profiles.wordpress.org/marcusquinn/', 'avatar' => 'https://secure.gravatar.com/avatar/', 'display_name' => 'Marcus Quinn' ), 'wpallstars' => array( 'profile' => 'https://profiles.wordpress.org/wpallstars/', 'avatar' => 'https://secure.gravatar.com/avatar/', 'display_name' => 'WPALLSTARS' ) ); // Add a random number and timestamp to force cache refresh $new_result->download_link = 'https://www.wpallstars.com/plugins/wp-fix-plugin-does-not-exist-notices.zip?v=' . FPDEN_VERSION . '&cb=' . mt_rand(1000000, 9999999) . '&t=' . time(); // Add active installations count $new_result->active_installs = 1000; // Add rating information $new_result->rating = 100; $new_result->num_ratings = 5; $new_result->ratings = array( 5 => 5, 4 => 0, 3 => 0, 2 => 0, 1 => 0 ); // Add homepage and download link $new_result->homepage = 'https://www.wpallstars.com'; // Set no caching $new_result->cache_time = 0; // Return our completely new result object return $new_result; } return $result; } /** * Check if a slug matches one of our missing plugins. * * @param string $slug The plugin slug to check. * @param array $invalid_plugins List of invalid plugin paths. * @return bool True if the slug matches a missing plugin. */ private function is_missing_plugin($slug, $invalid_plugins) { foreach ($invalid_plugins as $plugin_file) { // Extract the plugin slug from the plugin file path $plugin_slug = dirname($plugin_file); if ('.' === $plugin_slug) { $plugin_slug = basename($plugin_file, '.php'); } if ($slug === $plugin_slug) { return true; } } return false; } /** * Prevent WordPress from caching our plugin API responses. * * @param object|WP_Error $result The result object or WP_Error. * @param string $action The type of information being requested. * @param object $args Plugin API arguments. * @return object|WP_Error The result object or WP_Error. */ public function prevent_plugins_api_caching($result, $action, $args) { // Only modify plugin_information requests if ('plugin_information' !== $action) { return $result; } // Check if we have a slug to work with if (empty($args->slug)) { return $result; } // Get our list of invalid plugins $invalid_plugins = $this->get_invalid_plugins(); // Check if the requested plugin is one of our missing plugins foreach ($invalid_plugins as $plugin_file) { // Extract the plugin slug from the plugin file path $plugin_slug = dirname($plugin_file); if ('.' === $plugin_slug) { $plugin_slug = basename($plugin_file, '.php'); } // If this is one of our missing plugins, prevent caching if ($args->slug === $plugin_slug) { // Add a filter to prevent caching of this response add_filter('plugins_api_result_' . $args->slug, '__return_false'); // Add a timestamp to force cache busting if (is_object($result)) { $result->last_updated = current_time('mysql'); $result->cache_time = 0; } } } return $result; } /** * Clear plugin API cache when viewing the plugins page. * * @return void */ public function maybe_clear_plugin_api_cache() { // Only run on the plugins page if (!$this->is_plugins_page()) { return; } // Get our list of invalid plugins $invalid_plugins = $this->get_invalid_plugins(); // Clear transients for each invalid plugin foreach ($invalid_plugins as $plugin_file) { // Extract the plugin slug from the plugin file path $plugin_slug = dirname($plugin_file); if ('.' === $plugin_slug) { $plugin_slug = basename($plugin_file, '.php'); } // Delete all possible transients for this plugin delete_transient('plugins_api_' . $plugin_slug); delete_site_transient('plugins_api_' . $plugin_slug); delete_transient('plugin_information_' . $plugin_slug); delete_site_transient('plugin_information_' . $plugin_slug); // Clear any other transients that might be caching plugin info $this->clear_all_plugin_transients(); } // Also clear our own plugin's cache $this->clear_own_plugin_cache(); } /** * Clear all plugin-related transients that might be caching information. * * @return void */ private function clear_all_plugin_transients() { // Clear update cache delete_site_transient('update_plugins'); delete_site_transient('update_themes'); delete_site_transient('update_core'); // Clear plugins API cache delete_site_transient('plugin_information'); // Clear plugin update counts delete_transient('plugin_updates_count'); delete_site_transient('plugin_updates_count'); // Clear plugin slugs cache delete_transient('plugin_slugs'); delete_site_transient('plugin_slugs'); } /** * Clear cache specifically for our own plugin. * * @return void */ private function clear_own_plugin_cache() { // Clear our own plugin's cache (both old and new slugs) $our_slugs = array('wp-fix-plugin-does-not-exist-notices', 'fix-plugin-does-not-exist-notices'); foreach ($our_slugs as $slug) { delete_transient('plugins_api_' . $slug); delete_site_transient('plugins_api_' . $slug); delete_transient('plugin_information_' . $slug); delete_site_transient('plugin_information_' . $slug); } // Clear plugin update transients delete_site_transient('update_plugins'); delete_site_transient('plugin_information'); // Force refresh of plugin update information if function exists if (function_exists('wp_clean_plugins_cache')) { wp_clean_plugins_cache(true); } // Clear object cache if function exists if (function_exists('wp_cache_flush')) { wp_cache_flush(); } } }