get_invalid_plugins(); if ( empty( $invalid_plugins ) ) { return; // No missing plugins, no need for the special notice JS/CSS. } wp_enqueue_style( 'fpden-admin-styles', FPDEN_PLUGIN_URL . 'assets/css/admin-styles.css', array(), FPDEN_VERSION ); wp_enqueue_script( 'fpden-admin-scripts', FPDEN_PLUGIN_URL . 'assets/js/admin-scripts.js', array( 'jquery' ), // Add dependencies if needed, e.g., jQuery. FPDEN_VERSION, true // Load in footer. ); // Add translation strings for JavaScript wp_localize_script( 'fpden-admin-scripts', 'fpdenData', array( 'i18n' => array( 'clickToScroll' => esc_html__( 'Click here to scroll to missing plugins', 'wp-fix-plugin-does-not-exist-notices' ), 'pluginMissing' => esc_html__( 'File Missing', 'wp-fix-plugin-does-not-exist-notices' ), 'removeNotice' => esc_html__( 'Remove Notice', 'wp-fix-plugin-does-not-exist-notices' ), ), ) ); } /** * 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 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' => __( 'N/A', 'wp-fix-plugin-does-not-exist-notices' ), 'Author' => '', 'PluginURI' => '', 'AuthorURI' => '', 'Title' => $plugin_name . ' (' . __( 'Missing', 'wp-fix-plugin-does-not-exist-notices' ) . ')', 'AuthorName' => '', ); } } 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. * @noinspection PhpUnusedParameterInspection */ 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'] ) { ?>

invalid_plugins ) { return $this->invalid_plugins; } $this->invalid_plugins = array(); $active_plugins = array(); // Determine which option to check based on context (Network Admin or single site). if ( is_multisite() && is_network_admin() ) { // Network active plugins are stored as keys in an associative array. $active_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) ); } else { // Single site active plugins are stored in a numerically indexed array. $active_plugins = get_option( 'active_plugins', array() ); } // Check if the file exists for each active plugin. foreach ( $active_plugins as $plugin_file ) { // Construct the full path to the main plugin file. $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; // Use validate_file to prevent directory traversal issues, although less likely here. if ( validate_file( $plugin_file ) === 0 && ! file_exists( $plugin_path ) ) { $this->invalid_plugins[] = $plugin_file; } } return $this->invalid_plugins; } // We've removed the prevent_auto_deactivation method as it was causing critical errors } // End class Fix_Plugin_Does_Not_Exist_Notices // Initialize the plugin class. new Fix_Plugin_Does_Not_Exist_Notices(); // Initialize the updater if composer autoload exists $autoloader = __DIR__ . '/vendor/autoload.php'; if (file_exists($autoloader)) { require_once $autoloader; // Initialize the updater if the class exists if (class_exists('\WPALLSTARS\FixPluginDoesNotExistNotices\Updater')) { new \WPALLSTARS\FixPluginDoesNotExistNotices\Updater(__FILE__); } }