Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
cd593f68d3 |
15
CHANGELOG.md
15
CHANGELOG.md
@ -2,6 +2,21 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.3.0] - 2023-10-05
|
||||
### Changed
|
||||
- Complete redesign for maximum compatibility with all WordPress themes
|
||||
- Now uses the plugins list table for missing plugin references
|
||||
- Uses standard WordPress admin UI patterns and hooks
|
||||
|
||||
### Added
|
||||
- Missing plugins now appear directly in the plugins list
|
||||
- "Remove Reference" action link in the plugins list
|
||||
- Success/error notices after removing references
|
||||
|
||||
### Fixed
|
||||
- Compatibility issues with various WordPress admin themes
|
||||
- Reliability issues with notification detection
|
||||
|
||||
## [1.2.4] - 2023-10-05
|
||||
### Fixed
|
||||
- Compatibility with more WordPress admin UI variations
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Plugin Reference Cleaner
|
||||
Author: Marcus Quinn
|
||||
Author URI: https://www.wpallstars.com
|
||||
Version: 1.2.4
|
||||
Version: 1.3.0
|
||||
License: GPL-2.0+
|
||||
|
||||
## Description
|
||||
@ -47,6 +47,13 @@ If you don't have this notification perpetually showing on your /wp-admin/plugin
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.3.0
|
||||
* Complete redesign for maximum compatibility with all WordPress themes
|
||||
* Now adds missing plugins directly to the plugins list table
|
||||
* Uses standard WordPress admin UI patterns instead of DOM manipulation
|
||||
* Added "Remove Reference" action link in the plugins list
|
||||
* Significantly improved reliability across all WordPress configurations
|
||||
|
||||
### 1.2.4
|
||||
* Fixed compatibility with more WordPress admin themes
|
||||
* Added advanced DOM traversal to find error messages
|
||||
|
@ -2,7 +2,7 @@
|
||||
/*
|
||||
* Plugin Name: Plugin Reference Cleaner
|
||||
* Description: Adds a "Remove Reference" button to plugin deactivation error notices, allowing users to clean up invalid plugin entries.
|
||||
* Version: 1.2.4
|
||||
* Version: 1.3.0
|
||||
* Author: Marcus Quinn
|
||||
* Author URI: https://www.wpallstars.com
|
||||
* License: GPL-2.0+
|
||||
@ -15,252 +15,108 @@ if (!defined('ABSPATH')) {
|
||||
|
||||
class Plugin_Reference_Cleaner {
|
||||
public function __construct() {
|
||||
// Only hook into admin actions when on the plugins page
|
||||
add_action('current_screen', function($screen) {
|
||||
if (isset($screen->id) && ($screen->id === 'plugins' || $screen->id === 'plugins-network')) {
|
||||
// Hook into admin notices to modify plugin error messages
|
||||
add_action('admin_notices', array($this, 'inject_remove_button'), 100);
|
||||
add_action('network_admin_notices', array($this, 'inject_remove_button'), 100);
|
||||
}
|
||||
});
|
||||
// Add our plugin to the plugins list
|
||||
add_filter('all_plugins', array($this, 'add_missing_plugins_references'));
|
||||
|
||||
// Handle the AJAX request to remove the plugin reference
|
||||
add_action('wp_ajax_remove_plugin_reference', array($this, 'remove_plugin_reference'));
|
||||
// Add our action link to the plugins list
|
||||
add_filter('plugin_action_links', array($this, 'add_remove_reference_action'), 20, 4);
|
||||
|
||||
// Handle the remove reference action
|
||||
add_action('admin_init', array($this, 'handle_remove_reference'));
|
||||
|
||||
// Add admin notices for operation feedback
|
||||
add_action('admin_notices', array($this, 'admin_notices'));
|
||||
}
|
||||
|
||||
// Inject "Remove Reference" button only if a relevant notice exists
|
||||
public function inject_remove_button() {
|
||||
// Check if a "Plugin file does not exist" notice exists
|
||||
$notices = $this->get_admin_notices();
|
||||
$has_error_notice = false;
|
||||
$plugin_files = array();
|
||||
/**
|
||||
* Find and add invalid plugin references to the plugins list
|
||||
*/
|
||||
public function add_missing_plugins_references($plugins) {
|
||||
// Only run on the plugins page
|
||||
if (!$this->is_plugins_page()) {
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
if (!empty($notices)) {
|
||||
foreach ($notices as $notice) {
|
||||
// Look for both variations of the error message
|
||||
if (strpos($notice, 'has been deactivated due to an error: Plugin file does not exist') !== false ||
|
||||
strpos($notice, 'deactivated due to an error: Plugin file does not exist') !== false) {
|
||||
// Extract plugin file from notice using various patterns
|
||||
if (preg_match('/The plugin ([^ ]+)/', $notice, $match)) {
|
||||
$plugin_files[] = $match[1];
|
||||
$has_error_notice = true;
|
||||
}
|
||||
}
|
||||
// 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 . ' <span class="error">(File Missing)</span>',
|
||||
'Description' => 'This plugin file does not exist. You can safely remove this reference.',
|
||||
'Version' => 'N/A',
|
||||
'Author' => '',
|
||||
'PluginURI' => '',
|
||||
'AuthorURI' => '',
|
||||
'Title' => $plugin_name . ' (Missing)',
|
||||
'AuthorName' => ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Only proceed if a relevant notice was found
|
||||
if (!$has_error_notice || empty($plugin_files)) {
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Remove Reference action link to invalid plugins
|
||||
*/
|
||||
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
|
||||
if (isset($plugin_data['Name']) && strpos($plugin_data['Name'], '<span class="error">(File Missing)</span>') !== false) {
|
||||
// Clear existing actions
|
||||
$actions = array();
|
||||
|
||||
// Add our 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);
|
||||
$actions['remove_reference'] = '<a href="' . esc_url($remove_url) . '" class="delete" aria-label="' . esc_attr__('Remove Reference', 'plugin-reference-cleaner') . '">Remove Reference</a>';
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the remove reference action
|
||||
*/
|
||||
public function handle_remove_reference() {
|
||||
// Check if we're removing a reference
|
||||
if (!isset($_GET['action']) || $_GET['action'] !== 'remove_reference' || !isset($_GET['plugin'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject JavaScript with the specific plugin files
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var pluginFiles = <?php echo wp_json_encode($plugin_files); ?>;
|
||||
console.log('Plugin Reference Cleaner: Detected plugin files:', pluginFiles);
|
||||
|
||||
// Get all notifications directly in the plugins page
|
||||
var pluginsPage = document.querySelector('.wrap');
|
||||
if (!pluginsPage) {
|
||||
console.log('Plugin Reference Cleaner: Could not find the plugins page wrapper');
|
||||
return;
|
||||
}
|
||||
|
||||
// Look specifically for elements containing the error message
|
||||
// Use direct DOM traversal to find all text nodes
|
||||
function findTextNodesWithText(element, searchText) {
|
||||
var result = [];
|
||||
var walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
||||
var node;
|
||||
|
||||
while (node = walk.nextNode()) {
|
||||
if (node.nodeValue.indexOf(searchText) > -1) {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find paragraphs containing error messages
|
||||
var errorTexts = findTextNodesWithText(pluginsPage, 'Plugin file does not exist');
|
||||
console.log('Plugin Reference Cleaner: Found error nodes:', errorTexts.length);
|
||||
|
||||
if (errorTexts.length === 0) {
|
||||
// Try another approach - find direct error messages
|
||||
var allElements = pluginsPage.querySelectorAll('*');
|
||||
for (var i = 0; i < allElements.length; i++) {
|
||||
var el = allElements[i];
|
||||
var text = el.textContent;
|
||||
if (text && text.includes('has been deactivated due to an error: Plugin file does not exist')) {
|
||||
errorTexts.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle each error message
|
||||
errorTexts.forEach(function(textNode) {
|
||||
console.log('Plugin Reference Cleaner: Processing error node:', textNode);
|
||||
|
||||
// Get the parent element of the text node
|
||||
var errorElement = textNode.parentNode || textNode;
|
||||
|
||||
// Check if a button already exists to avoid duplicates
|
||||
if (errorElement.querySelector('.remove-plugin-ref')) {
|
||||
console.log('Plugin Reference Cleaner: Button already exists for this element');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find which plugin file this error refers to
|
||||
var matchingPluginFile = null;
|
||||
pluginFiles.forEach(function(pluginFile) {
|
||||
if (textNode.nodeValue && textNode.nodeValue.includes(pluginFile) ||
|
||||
(textNode.textContent && textNode.textContent.includes(pluginFile))) {
|
||||
matchingPluginFile = pluginFile;
|
||||
}
|
||||
});
|
||||
|
||||
// If we couldn't match a specific plugin, use the first one as fallback
|
||||
if (!matchingPluginFile && pluginFiles.length > 0) {
|
||||
matchingPluginFile = pluginFiles[0];
|
||||
console.log('Plugin Reference Cleaner: Using fallback plugin file:', matchingPluginFile);
|
||||
}
|
||||
|
||||
if (matchingPluginFile) {
|
||||
// Create button
|
||||
var button = document.createElement('button');
|
||||
button.textContent = 'Remove Reference';
|
||||
button.className = 'button button-secondary remove-plugin-ref';
|
||||
button.dataset.plugin = matchingPluginFile;
|
||||
button.style.marginLeft = '10px';
|
||||
|
||||
// Append the button to the error message
|
||||
errorElement.appendChild(button);
|
||||
console.log('Plugin Reference Cleaner: Added button for ' + matchingPluginFile + ' to', errorElement);
|
||||
} else {
|
||||
console.log('Plugin Reference Cleaner: Could not determine plugin file for this error');
|
||||
}
|
||||
});
|
||||
|
||||
// Inject a more direct approach if the above fails
|
||||
if (errorTexts.length === 0) {
|
||||
// Target the specific format in the screenshot
|
||||
var errorMessages = document.querySelectorAll('.wrap > .notice, .wrap > div > .notice');
|
||||
console.log('Plugin Reference Cleaner: Trying direct approach, found notices:', errorMessages.length);
|
||||
|
||||
errorMessages.forEach(function(notice) {
|
||||
var text = notice.textContent;
|
||||
if (text && text.includes('Plugin file does not exist')) {
|
||||
// Extract the plugin path
|
||||
var pluginMatch = /The plugin ([^ ]+) has been deactivated/.exec(text);
|
||||
var pluginFile = pluginMatch ? pluginMatch[1] : (pluginFiles.length > 0 ? pluginFiles[0] : null);
|
||||
|
||||
if (pluginFile) {
|
||||
// Get container to add button
|
||||
var container = notice.querySelector('p') || notice;
|
||||
|
||||
if (!container.querySelector('.remove-plugin-ref')) {
|
||||
var button = document.createElement('button');
|
||||
button.textContent = 'Remove Reference';
|
||||
button.className = 'button button-secondary remove-plugin-ref';
|
||||
button.dataset.plugin = pluginFile;
|
||||
button.style.marginLeft = '10px';
|
||||
container.appendChild(button);
|
||||
console.log('Plugin Reference Cleaner: Direct approach - added button for ' + pluginFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add click event listeners to all created buttons
|
||||
document.querySelectorAll('.remove-plugin-ref').forEach(function(button) {
|
||||
button.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
var pluginFile = this.dataset.plugin;
|
||||
if (confirm('Are you sure you want to remove the reference to ' + pluginFile + '?')) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<?php echo esc_url(admin_url('admin-ajax.php')); ?>', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
alert('Plugin reference removed successfully.');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to remove plugin reference: ' + (response.data || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed to parse server response.');
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
alert('Failed to remove plugin reference. Server returned status ' + xhr.status);
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
alert('Network error occurred while trying to remove plugin reference.');
|
||||
};
|
||||
xhr.send('action=remove_plugin_reference&plugin=' + encodeURIComponent(pluginFile) + '&nonce=<?php echo wp_create_nonce('remove_plugin_reference'); ?>');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Helper function to capture admin notices
|
||||
private function get_admin_notices() {
|
||||
// Static flag to prevent infinite recursion
|
||||
static $is_capturing = false;
|
||||
|
||||
// If already capturing, return empty to break potential loops
|
||||
if ($is_capturing) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$is_capturing = true;
|
||||
|
||||
ob_start();
|
||||
do_action('admin_notices');
|
||||
do_action('network_admin_notices');
|
||||
$output = ob_get_clean();
|
||||
|
||||
$is_capturing = false;
|
||||
|
||||
if (empty($output)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_filter(explode("\n", $output));
|
||||
}
|
||||
|
||||
// Handle the AJAX request to remove the plugin reference
|
||||
public function remove_plugin_reference() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'remove_plugin_reference')) {
|
||||
wp_send_json_error('Invalid security token. Please refresh the page and try again.');
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Check user permissions
|
||||
// Verify permissions
|
||||
if (!current_user_can('activate_plugins')) {
|
||||
wp_send_json_error('You do not have sufficient permissions to perform this action.');
|
||||
wp_die();
|
||||
wp_die(__('You do not have sufficient permissions to perform this action.', 'plugin-reference-cleaner'));
|
||||
}
|
||||
|
||||
// Get and validate plugin file parameter
|
||||
$plugin_file = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : '';
|
||||
if (empty($plugin_file)) {
|
||||
wp_send_json_error('No plugin specified.');
|
||||
wp_die();
|
||||
// Get the plugin file
|
||||
$plugin_file = isset($_GET['plugin']) ? $_GET['plugin'] : '';
|
||||
|
||||
// Verify nonce
|
||||
check_admin_referer('remove_plugin_reference_' . $plugin_file);
|
||||
|
||||
// Remove the plugin reference
|
||||
$success = $this->remove_plugin_reference($plugin_file);
|
||||
|
||||
// Redirect back to plugins page with a message
|
||||
$redirect = admin_url('plugins.php');
|
||||
$redirect = add_query_arg($success ? 'reference_removed' : 'reference_removal_failed', '1', $redirect);
|
||||
wp_redirect($redirect);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a plugin reference from the active plugins
|
||||
*/
|
||||
public function remove_plugin_reference($plugin_file) {
|
||||
$success = false;
|
||||
|
||||
// Handle multisite network admin
|
||||
@ -282,13 +138,59 @@ class Plugin_Reference_Cleaner {
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
wp_send_json_success('Plugin reference removed successfully.');
|
||||
} else {
|
||||
wp_send_json_error('Plugin reference not found or could not be removed.');
|
||||
return $success;
|
||||
}
|
||||
|
||||
wp_die();
|
||||
/**
|
||||
* Display admin notices
|
||||
*/
|
||||
public function admin_notices() {
|
||||
// Only run on the plugins page
|
||||
if (!$this->is_plugins_page()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
if (isset($_GET['reference_removed']) && $_GET['reference_removed'] === '1') {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>Plugin reference removed successfully.</p></div>';
|
||||
}
|
||||
|
||||
// Show error message
|
||||
if (isset($_GET['reference_removal_failed']) && $_GET['reference_removal_failed'] === '1') {
|
||||
echo '<div class="notice notice-error is-dismissible"><p>Failed to remove plugin reference. The plugin may already have been removed.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're on the plugins page
|
||||
*/
|
||||
private function is_plugins_page() {
|
||||
global $pagenow;
|
||||
return is_admin() && $pagenow === 'plugins.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of invalid plugin references
|
||||
*/
|
||||
private function get_invalid_plugins() {
|
||||
$invalid_plugins = array();
|
||||
|
||||
// Get all active plugins
|
||||
if (is_multisite() && is_network_admin()) {
|
||||
$active_plugins = array_keys(get_site_option('active_sitewide_plugins', array()));
|
||||
} else {
|
||||
$active_plugins = get_option('active_plugins', array());
|
||||
}
|
||||
|
||||
// Check if each plugin exists
|
||||
foreach ($active_plugins as $plugin) {
|
||||
$plugin_path = WP_PLUGIN_DIR . '/' . $plugin;
|
||||
if (!file_exists($plugin_path)) {
|
||||
$invalid_plugins[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return $invalid_plugins;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
=== Plugin Reference Cleaner ===
|
||||
Author: Marcus Quinn
|
||||
Author URI: https://www.wpallstars.com
|
||||
Version: 1.2.4
|
||||
Version: 1.3.0
|
||||
License: GPL-2.0+
|
||||
|
||||
== Description ==
|
||||
@ -47,6 +47,13 @@ If you don't have this notification perpetually showing on your /wp-admin/plugin
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.3.0 =
|
||||
* Complete redesign for maximum compatibility with all WordPress themes
|
||||
* Now adds missing plugins directly to the plugins list table
|
||||
* Uses standard WordPress admin UI patterns instead of DOM manipulation
|
||||
* Added "Remove Reference" action link in the plugins list
|
||||
* Significantly improved reliability across all WordPress configurations
|
||||
|
||||
= 1.2.4 =
|
||||
* Fixed compatibility with more WordPress admin themes
|
||||
* Added advanced DOM traversal to find error messages
|
||||
|
Reference in New Issue
Block a user