YahnisElsts / plugin-update-checker

A custom update checker for WordPress plugins. Useful if you don't want to host your project in the official WP repository, but would still like it to support automatic updates. Despite the name, it also works with themes.
MIT License
2.22k stars 403 forks source link

fatal error when uninstalling a plugin #516

Closed liedekef closed 1 year ago

liedekef commented 1 year ago

When a plugin using plugin-udate-checker is being uninstalled, it triggers a php error:


[14-Jan-2023 22:14:36 UTC] PHP Fatal error:  Uncaught Error: Class "YahnisElsts\PluginUpdateChecker\v5p0\Utils" not found in /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/StateStore.php:173
Stack trace:
#0 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/StateStore.php(156): YahnisElsts\PluginUpdateChecker\v5p0\StateStore->load()
#1 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/StateStore.php(72): YahnisElsts\PluginUpdateChecker\v5p0\StateStore->lazyLoad()
#2 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/UpdateChecker.php(312): YahnisElsts\PluginUpdateChecker\v5p0\StateStore->getUpdate()
#3 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/Plugin/UpdateChecker.php(298): YahnisElsts\PluginUpdateChecker\v5p0\UpdateChecker->getUpdate()
#4 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/Vcs/VcsCheckerMethods.php(42): YahnisElsts\PluginUpdateChecker\v5p0\Plugin\UpdateChecker->getUpdate()
#5 /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/UpdateChecker.php(529): YahnisElsts\PluginUpdateChecker\v5p0\Vcs\PluginUpdateChecker->getUpdate()
#6 /var/www/html/wp_test/wp-includes/class-wp-hook.php(310): YahnisElsts\PluginUpdateChecker\v5p0\UpdateChecker->injectUpdate()
#7 /var/www/html/wp_test/wp-includes/plugin.php(205): WP_Hook->apply_filters()
#8 /var/www/html/wp_test/wp-includes/option.php(1988): apply_filters()
#9 /var/www/html/wp_test/wp-admin/includes/plugin.php(1018): get_site_transient()
#10 /var/www/html/wp_test/wp-admin/includes/ajax-actions.php(4698): delete_plugins()
#11 /var/www/html/wp_test/wp-includes/class-wp-hook.php(308): wp_ajax_delete_plugin()
#12 /var/www/html/wp_test/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()
#13 /var/www/html/wp_test/wp-includes/plugin.php(517): WP_Hook->do_action()
#14 /var/www/html/wp_test/wp-admin/admin-ajax.php(188): do_action()
#15 {main}
  thrown in /var/www/html/wp_test/wp-content/plugins/my-test-plugin/plugin-update-checker/Puc/v5p0/StateStore.php on line 173

Currently I worked around this by changing the recommended code in my main plugin file from:

require_once 'plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
// include our custom update checker code
$myUpdateChecker = PucFactory::buildUpdateChecker(
        'https://github.com/xxxx/my-test-plugin/',
        __FILE__,
        'my-test-plugin'
);

to:

require_once 'plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;
// include our custom update checker code
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
        $myUpdateChecker = PucFactory::buildUpdateChecker(
                'https://github.com/xxxx/my-test-plugin/',
                __FILE__,
                'my-test-plugin'
        );
}

So, in fact checking if not in uninstall mode. But this should probably be done better and inside the plugin update script itself I guess, no? Is this even a recommended method?

YahnisElsts commented 1 year ago

Typically, a plugin has to be deactivated before it can be uninstalled. If the update checker is set up as part of a plugin (which seems to be the case here since you're using the __FILE__ constant), it would not be running when the plugin is deactivated, and it would be impossible for it to trigger any errors.

Do you have any ideas about how the update checker code could be executed while the plugin is being deleted?

liedekef commented 1 year ago

I'm just using the code in my main plugin file as you say in the doc. See https://github.com/liedekef/events-made-easy/blob/main/events-manager.php

YahnisElsts commented 1 year ago

Right, so how is the code in your main plugin file running when the plugin is inactive? Does the plugin have an uninstallation script (uninstall.php) or something that also loads the main plugin file?

liedekef commented 1 year ago

There is an uninstall.php and indeed there I load the main plugin file (because all other code/functions/defines are done from the main file). I'm in fact re-using the function that handles the plugin deactivation to also delete the tables, options and cron-definitions. Loading the main plugin file gives me access to all the constants, functions and more needed to do a clean uninstall. So I now understand a bit more of the reason behind this. I'm guessing the WP_UNINSTALL_PLUGIN-check is then ok to use, since it is probably only defined when uninstall.php is being run.

YahnisElsts commented 1 year ago

All right, that makes sense. The update checker has some built-in safeguards that automatically remove its hooks when using an uninstallation hook, but those don't work with uninstall.php. In this case, you would need to either completely avoid initializing the update checker when the plugin is being uninstalled, or call $myUpdateChecker->removeHooks() inside your uninstallation script.

Technically, WP_UNINSTALL_PLUGIN will be defined when any plugin that has an uninstall.php file is being uninstalled, but I don't know if it's worth worrying about that in this case.

liedekef commented 1 year ago

Thanks for the clarification. I'm currently splitting out the code a bit more, so I can include another file that doesn't trigger the update-functions (and more), so in fact it should be much better after that :-)