From 9931919237c76d35143d717f0a5c078df6632ee2 Mon Sep 17 00:00:00 2001 From: Yahnis Elsts Date: Wed, 13 Jan 2016 18:03:06 +0200 Subject: [PATCH] Extract scheduling logic to a separate class. WIP. --- debug-bar-panel.php | 15 +- plugin-update-checker.php | 329 ++++++++++++++++++++++---------------- 2 files changed, 195 insertions(+), 149 deletions(-) diff --git a/debug-bar-panel.php b/debug-bar-panel.php index 135efae..3f141f8 100644 --- a/debug-bar-panel.php +++ b/debug-bar-panel.php @@ -48,20 +48,21 @@ class PluginUpdateCheckerPanel extends Debug_Bar_Panel { } $this->row('Metadata URL', htmlentities($this->updateChecker->metadataUrl) . ' ' . $requestInfoButton . $this->responseBox); - if ( $this->updateChecker->checkPeriod > 0 ) { - $this->row('Automatic checks', 'Every ' . $this->updateChecker->checkPeriod . ' hours'); + $scheduler = $this->updateChecker->scheduler; + if ( $scheduler->checkPeriod > 0 ) { + $this->row('Automatic checks', 'Every ' . $scheduler->checkPeriod . ' hours'); } else { $this->row('Automatic checks', 'Disabled'); } - if ( isset($this->updateChecker->throttleRedundantChecks) ) { - if ( $this->updateChecker->throttleRedundantChecks && ($this->updateChecker->checkPeriod > 0) ) { + if ( isset($scheduler->throttleRedundantChecks) ) { + if ( $scheduler->throttleRedundantChecks && ($scheduler->checkPeriod > 0) ) { $this->row( 'Throttling', sprintf( 'Enabled. If an update is already available, check for updates every %1$d hours instead of every %2$d hours.', - $this->updateChecker->throttledCheckPeriod, - $this->updateChecker->checkPeriod + $scheduler->throttledCheckPeriod, + $scheduler->checkPeriod ) ); } else { @@ -86,7 +87,7 @@ class PluginUpdateCheckerPanel extends Debug_Bar_Panel { $this->row('Last check', 'Never'); } - $nextCheck = wp_next_scheduled($this->updateChecker->getCronHookName()); + $nextCheck = wp_next_scheduled($this->updateChecker->scheduler->getCronHookName()); $this->row('Next automatic check', $this->formatTimeWithDelta($nextCheck)); if ( isset($state, $state->checkedVersion) ) { diff --git a/plugin-update-checker.php b/plugin-update-checker.php index 58225b9..42a358f 100644 --- a/plugin-update-checker.php +++ b/plugin-update-checker.php @@ -22,17 +22,13 @@ class PluginUpdateChecker_2_3 { public $pluginAbsolutePath = ''; //Full path of the main plugin file. public $pluginFile = ''; //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins. public $slug = ''; //Plugin slug. - public $checkPeriod = 12; //How often to check for updates (in hours). public $optionName = ''; //Where to store the update info. public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory. public $debugMode = false; //Set to TRUE to enable error reporting. Errors are raised using trigger_error() //and should be logged to the standard PHP error log. + public $scheduler; - public $throttleRedundantChecks = false; //Check less often if we already know that an update is available. - public $throttledCheckPeriod = 72; - - private $cronHook = null; private $debugBarPlugin = null; private $cachedInstalledVersion = null; @@ -55,7 +51,6 @@ class PluginUpdateChecker_2_3 { $this->pluginAbsolutePath = $pluginFile; $this->pluginFile = plugin_basename($this->pluginAbsolutePath); $this->muPluginFile = $muPluginFile; - $this->checkPeriod = $checkPeriod; $this->slug = $slug; $this->optionName = $optionName; $this->debugMode = (bool)(constant('WP_DEBUG')); @@ -75,9 +70,24 @@ class PluginUpdateChecker_2_3 { if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) { $this->muPluginFile = $this->pluginFile; } - + + $this->scheduler = $this->createScheduler($checkPeriod); + $this->installHooks(); } + + /** + * Create an instance of the scheduler. + * + * This is implemented as a method to make it possible for plugins to subclass the update checker + * and substitute their own scheduler. + * + * @param int $checkPeriod + * @return PucScheduler + */ + protected function createScheduler($checkPeriod) { + return new PucScheduler($this, $checkPeriod); + } /** * Install the hooks required to run periodic update checks and inject update info @@ -101,50 +111,6 @@ class PluginUpdateChecker_2_3 { add_filter('upgrader_post_install', array($this, 'clearCachedVersion')); add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); - //Set up the periodic update checks - $this->cronHook = 'check_plugin_updates-' . $this->slug; - if ( $this->checkPeriod > 0 ){ - - //Trigger the check via Cron. - //Try to use one of the default schedules if possible as it's less likely to conflict - //with other plugins and their custom schedules. - $defaultSchedules = array( - 1 => 'hourly', - 12 => 'twicedaily', - 24 => 'daily', - ); - if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) { - $scheduleName = $defaultSchedules[$this->checkPeriod]; - } else { - //Use a custom cron schedule. - $scheduleName = 'every' . $this->checkPeriod . 'hours'; - add_filter('cron_schedules', array($this, '_addCustomSchedule')); - } - - if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) { - wp_schedule_event(time(), $scheduleName, $this->cronHook); - } - add_action($this->cronHook, array($this, 'maybeCheckForUpdates')); - - register_deactivation_hook($this->pluginFile, array($this, '_removeUpdaterCron')); - - //In case Cron is disabled or unreliable, we also manually trigger - //the periodic checks while the user is browsing the Dashboard. - add_action( 'admin_init', array($this, 'maybeCheckForUpdates') ); - - //Like WordPress itself, we check more often on certain pages. - /** @see wp_update_plugins */ - add_action('load-update-core.php', array($this, 'maybeCheckForUpdates')); - add_action('load-plugins.php', array($this, 'maybeCheckForUpdates')); - add_action('load-update.php', array($this, 'maybeCheckForUpdates')); - //This hook fires after a bulk update is complete. - add_action('upgrader_process_complete', array($this, 'maybeCheckForUpdates'), 11, 0); - - } else { - //Periodic checks are disabled. - wp_clear_scheduled_hook($this->cronHook); - } - if ( did_action('plugins_loaded') ) { $this->initDebugBarPanel(); } else { @@ -165,41 +131,6 @@ class PluginUpdateChecker_2_3 { add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2); } - /** - * Add our custom schedule to the array of Cron schedules used by WP. - * - * @param array $schedules - * @return array - */ - public function _addCustomSchedule($schedules){ - if ( $this->checkPeriod && ($this->checkPeriod > 0) ){ - $scheduleName = 'every' . $this->checkPeriod . 'hours'; - $schedules[$scheduleName] = array( - 'interval' => $this->checkPeriod * 3600, - 'display' => sprintf('Every %d hours', $this->checkPeriod), - ); - } - return $schedules; - } - - /** - * Remove the scheduled cron event that the library uses to check for updates. - * - * @return void - */ - public function _removeUpdaterCron(){ - wp_clear_scheduled_hook($this->cronHook); - } - - /** - * Get the name of the update checker's WP-cron hook. Mostly useful for debugging. - * - * @return string - */ - public function getCronHookName() { - return $this->cronHook; - } - /** * Explicitly allow HTTP requests to the metadata URL. * @@ -413,62 +344,6 @@ class PluginUpdateChecker_2_3 { return $this->getUpdate(); } - /** - * Check for updates if the configured check interval has already elapsed. - * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron. - * - * You can override the default behaviour by using the "puc_check_now-$slug" filter. - * The filter callback will be passed three parameters: - * - Current decision. TRUE = check updates now, FALSE = don't check now. - * - Last check time as a Unix timestamp. - * - Configured check period in hours. - * Return TRUE to check for updates immediately, or FALSE to cancel. - * - * This method is declared public because it's a hook callback. Calling it directly is not recommended. - */ - public function maybeCheckForUpdates(){ - if ( empty($this->checkPeriod) ){ - return; - } - - $currentFilter = current_filter(); - if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) { - //Check more often when the user visits "Dashboard -> Updates" or does a bulk update. - $timeout = 60; - } else if ( in_array($currentFilter, array('load-plugins.php', 'load-update.php')) ) { - //Also check more often on the "Plugins" page and /wp-admin/update.php. - $timeout = 3600; - } else if ( $this->throttleRedundantChecks && ($this->getUpdate() !== null) ) { - //Check less frequently if it's already known that an update is available. - $timeout = $this->throttledCheckPeriod * 3600; - } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) { - //WordPress cron schedules are not exact, so lets do an update check even - //if slightly less than $checkPeriod hours have elapsed since the last check. - $cronFuzziness = 20 * 60; - $timeout = $this->checkPeriod * 3600 - $cronFuzziness; - } else { - $timeout = $this->checkPeriod * 3600; - } - - $state = $this->getUpdateState(); - $shouldCheck = - empty($state) || - !isset($state->lastCheck) || - ( (time() - $state->lastCheck) >= $timeout ); - - //Let plugin authors substitute their own algorithm. - $shouldCheck = apply_filters( - 'puc_check_now-' . $this->slug, - $shouldCheck, - (!empty($state) && isset($state->lastCheck)) ? $state->lastCheck : 0, - $this->checkPeriod - ); - - if ( $shouldCheck ){ - $this->checkForUpdates(); - } - } - /** * Load the update checker state from the DB. * @@ -1318,6 +1193,176 @@ class PluginUpdate_2_3 { endif; +if ( !class_exists('PucScheduler', false) ): + +/** + * The scheduler decides when and how often to check for updates. + * It calls @see PluginUpdateChecker_2_3::checkForUpdates() to perform the actual checks. + * + * @version 3.0 + */ +class PucScheduler { + public $checkPeriod = 12; //How often to check for updates (in hours). + public $throttleRedundantChecks = false; //Check less often if we already know that an update is available. + public $throttledCheckPeriod = 72; + + /** + * @var PluginUpdateChecker_2_3 + */ + protected $updateChecker; + + private $cronHook = null; + + /** + * Scheduler constructor. + * + * @param PluginUpdateChecker_2_3 $updateChecker + * @param int $checkPeriod How often to check for updates (in hours). + */ + public function __construct($updateChecker, $checkPeriod) { + $this->updateChecker = $updateChecker; + $this->checkPeriod = $checkPeriod; + + //Set up the periodic update checks + $this->cronHook = 'check_plugin_updates-' . $this->updateChecker->slug; + if ( $this->checkPeriod > 0 ){ + + //Trigger the check via Cron. + //Try to use one of the default schedules if possible as it's less likely to conflict + //with other plugins and their custom schedules. + $defaultSchedules = array( + 1 => 'hourly', + 12 => 'twicedaily', + 24 => 'daily', + ); + if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) { + $scheduleName = $defaultSchedules[$this->checkPeriod]; + } else { + //Use a custom cron schedule. + $scheduleName = 'every' . $this->checkPeriod . 'hours'; + add_filter('cron_schedules', array($this, '_addCustomSchedule')); + } + + if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) { + wp_schedule_event(time(), $scheduleName, $this->cronHook); + } + add_action($this->cronHook, array($this, 'maybeCheckForUpdates')); + + register_deactivation_hook($this->updateChecker->pluginFile, array($this, '_removeUpdaterCron')); + + //In case Cron is disabled or unreliable, we also manually trigger + //the periodic checks while the user is browsing the Dashboard. + add_action( 'admin_init', array($this, 'maybeCheckForUpdates') ); + + //Like WordPress itself, we check more often on certain pages. + /** @see wp_update_plugins */ + add_action('load-update-core.php', array($this, 'maybeCheckForUpdates')); + add_action('load-plugins.php', array($this, 'maybeCheckForUpdates')); + add_action('load-update.php', array($this, 'maybeCheckForUpdates')); + //This hook fires after a bulk update is complete. + add_action('upgrader_process_complete', array($this, 'maybeCheckForUpdates'), 11, 0); + + } else { + //Periodic checks are disabled. + wp_clear_scheduled_hook($this->cronHook); + } + } + + /** + * Check for updates if the configured check interval has already elapsed. + * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron. + * + * You can override the default behaviour by using the "puc_check_now-$slug" filter. + * The filter callback will be passed three parameters: + * - Current decision. TRUE = check updates now, FALSE = don't check now. + * - Last check time as a Unix timestamp. + * - Configured check period in hours. + * Return TRUE to check for updates immediately, or FALSE to cancel. + * + * This method is declared public because it's a hook callback. Calling it directly is not recommended. + */ + public function maybeCheckForUpdates(){ + if ( empty($this->checkPeriod) ){ + return; + } + + $currentFilter = current_filter(); + if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) { + //Check more often when the user visits "Dashboard -> Updates" or does a bulk update. + $timeout = 60; + } else if ( in_array($currentFilter, array('load-plugins.php', 'load-update.php')) ) { + //Also check more often on the "Plugins" page and /wp-admin/update.php. + $timeout = 3600; + } else if ( $this->throttleRedundantChecks && ($this->updateChecker->getUpdate() !== null) ) { + //Check less frequently if it's already known that an update is available. + $timeout = $this->throttledCheckPeriod * 3600; + } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) { + //WordPress cron schedules are not exact, so lets do an update check even + //if slightly less than $checkPeriod hours have elapsed since the last check. + $cronFuzziness = 20 * 60; + $timeout = $this->checkPeriod * 3600 - $cronFuzziness; + } else { + $timeout = $this->checkPeriod * 3600; + } + + $state = $this->updateChecker->getUpdateState(); + $shouldCheck = + empty($state) || + !isset($state->lastCheck) || + ( (time() - $state->lastCheck) >= $timeout ); + + //Let plugin authors substitute their own algorithm. + $shouldCheck = apply_filters( + 'puc_check_now-' . $this->updateChecker->slug, + $shouldCheck, + (!empty($state) && isset($state->lastCheck)) ? $state->lastCheck : 0, + $this->checkPeriod + ); + + if ( $shouldCheck ) { + $this->updateChecker->checkForUpdates(); + } + } + + /** + * Add our custom schedule to the array of Cron schedules used by WP. + * + * @param array $schedules + * @return array + */ + public function _addCustomSchedule($schedules){ + if ( $this->checkPeriod && ($this->checkPeriod > 0) ){ + $scheduleName = 'every' . $this->checkPeriod . 'hours'; + $schedules[$scheduleName] = array( + 'interval' => $this->checkPeriod * 3600, + 'display' => sprintf('Every %d hours', $this->checkPeriod), + ); + } + return $schedules; + } + + /** + * Remove the scheduled cron event that the library uses to check for updates. + * + * @return void + */ + public function _removeUpdaterCron(){ + wp_clear_scheduled_hook($this->cronHook); + } + + /** + * Get the name of the update checker's WP-cron hook. Mostly useful for debugging. + * + * @return string + */ + public function getCronHookName() { + return $this->cronHook; + } +} + +endif; + + if ( !class_exists('PucFactory', false) ): /**