From 5f251be064949ddce8a040a2bb9e4e1747000d95 Mon Sep 17 00:00:00 2001 From: Yahnis Elsts Date: Wed, 15 Nov 2023 18:49:00 +0200 Subject: [PATCH] Add more sanity checks to prevent PUC from inadvertently triggering a fatal error if one of its hook callbacks is called while the containing plugin/theme is being deleted. PUC already used `upgrader_process_complete` to remove hooks when the plugin version it was part of was deleted during an update. However, that did not catch more obscure situations, such as apparently being called from an unrelated AJAX request while the host plugin version was being deleted (a user sent a stack trace where it seems that was what happened). --- Puc/v5p3/Scheduler.php | 15 +++++++++++++++ Puc/v5p3/StateStore.php | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Puc/v5p3/Scheduler.php b/Puc/v5p3/Scheduler.php index 05d743e..79f20f2 100644 --- a/Puc/v5p3/Scheduler.php +++ b/Puc/v5p3/Scheduler.php @@ -187,6 +187,21 @@ if ( !class_exists(Scheduler::class, false) ): $state = $this->updateChecker->getUpdateState(); $shouldCheck = ($state->timeSinceLastCheck() >= $this->getEffectiveCheckPeriod()); + if ( $shouldCheck ) { + //Sanity check: Do not proceed if one of the critical classes is missing. + //That can happen - theoretically and extremely rarely - if maybeCoreUpdate() + //is called before the old version of our plugin has been fully deleted, or + //called from an independent AJAX request during deletion. + if ( !( + class_exists(Utils::class) + && class_exists(Metadata::class) + && class_exists(Plugin\Update::class) + && class_exists(Theme\Update::class) + ) ) { + return; + } + } + //Let plugin authors substitute their own algorithm. $shouldCheck = apply_filters( $this->updateChecker->getUniqueName('check_now'), diff --git a/Puc/v5p3/StateStore.php b/Puc/v5p3/StateStore.php index f182787..1391111 100644 --- a/Puc/v5p3/StateStore.php +++ b/Puc/v5p3/StateStore.php @@ -163,7 +163,12 @@ if ( !class_exists(StateStore::class, false) ): $state = get_site_option($this->optionName, null); - if ( !is_object($state) ) { + if ( + !is_object($state) + //Sanity check: If the Utils class is missing, the plugin is probably in the process + //of being deleted (e.g. the old version gets deleted during an update). + || !class_exists(Utils::class) + ) { $this->lastCheck = 0; $this->checkedVersion = ''; $this->update = null;