get_invalid_plugins(); if ( empty( $invalid_plugins ) ) { return; // No missing plugins, no need for the special notice JS/CSS. } $plugin_url = plugin_dir_url( __FILE__ ); wp_enqueue_style( 'fpden-admin-styles', $plugin_url . 'assets/css/admin-styles.css', array(), filemtime( plugin_dir_path( __FILE__ ) . 'assets/css/admin-styles.css' ) // Versioning based on file modification time. ); wp_enqueue_script( 'fpden-admin-scripts', $plugin_url . 'assets/js/admin-scripts.js', array( 'jquery' ), // Add dependencies if needed, e.g., jQuery. filemtime( plugin_dir_path( __FILE__ ) . 'assets/js/admin-scripts.js' ), // Versioning. true // Load in footer. ); // Optional: Pass localized data to script if needed. // wp_localize_script('fpden-admin-scripts', 'fpdenData', array( // 'ajax_url' => admin_url('admin-ajax.php'), // 'nonce' => wp_create_nonce('fpden_ajax_nonce'), // )); } /** * 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 ); $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 Reference" to permanently remove it from your active plugins list and eliminate the error notice.', 'fix-plugin-does-not-exist-notices' ), '/wp-content/plugins/' ), 'Version' => __( 'N/A', 'fix-plugin-does-not-exist-notices' ), 'Author' => '', 'PluginURI' => '', 'AuthorURI' => '', 'Title' => $plugin_name . ' (' . __( 'Missing', 'fix-plugin-does-not-exist-notices' ) . ')', 'AuthorName' => '', ); } } return $plugins; } /** * Add the Remove Reference action link to invalid plugins. * * Filters the action links displayed for each plugin on the plugins page. * Adds a "Remove Reference" 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; } // Check if this is a missing plugin identified by our previous filter. if ( isset( $plugin_data['Name'] ) && strpos( $plugin_data['Name'], '(File Missing)' ) !== false ) { // 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', 'fix-plugin-does-not-exist-notices' ), esc_attr( $plugin_file ) ); $actions['remove_reference'] = '' . esc_html__( 'Remove Reference', '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.', '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.', '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 informational notices about missing plugins and feedback * messages after attempting to remove a reference. * * @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'] ) { ?>

get_invalid_plugins(); // Display the main informational notice if there are missing plugins. if ( ! empty( $invalid_plugins ) ) { ?>

()