Scheduler.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <?php
  2. if ( !class_exists('Puc_v4p11_Scheduler', false) ):
  3. /**
  4. * The scheduler decides when and how often to check for updates.
  5. * It calls @see Puc_v4p11_UpdateChecker::checkForUpdates() to perform the actual checks.
  6. */
  7. class Puc_v4p11_Scheduler {
  8. public $checkPeriod = 12; //How often to check for updates (in hours).
  9. public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.
  10. public $throttledCheckPeriod = 72;
  11. protected $hourlyCheckHooks = array('load-update.php');
  12. /**
  13. * @var Puc_v4p11_UpdateChecker
  14. */
  15. protected $updateChecker;
  16. private $cronHook = null;
  17. /**
  18. * Scheduler constructor.
  19. *
  20. * @param Puc_v4p11_UpdateChecker $updateChecker
  21. * @param int $checkPeriod How often to check for updates (in hours).
  22. * @param array $hourlyHooks
  23. */
  24. public function __construct($updateChecker, $checkPeriod, $hourlyHooks = array('load-plugins.php')) {
  25. $this->updateChecker = $updateChecker;
  26. $this->checkPeriod = $checkPeriod;
  27. //Set up the periodic update checks
  28. $this->cronHook = $this->updateChecker->getUniqueName('cron_check_updates');
  29. if ( $this->checkPeriod > 0 ){
  30. //Trigger the check via Cron.
  31. //Try to use one of the default schedules if possible as it's less likely to conflict
  32. //with other plugins and their custom schedules.
  33. $defaultSchedules = array(
  34. 1 => 'hourly',
  35. 12 => 'twicedaily',
  36. 24 => 'daily',
  37. );
  38. if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {
  39. $scheduleName = $defaultSchedules[$this->checkPeriod];
  40. } else {
  41. //Use a custom cron schedule.
  42. $scheduleName = 'every' . $this->checkPeriod . 'hours';
  43. add_filter('cron_schedules', array($this, '_addCustomSchedule'));
  44. }
  45. if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {
  46. //Randomly offset the schedule to help prevent update server traffic spikes. Without this
  47. //most checks may happen during times of day when people are most likely to install new plugins.
  48. $firstCheckTime = time() - rand(0, max($this->checkPeriod * 3600 - 15 * 60, 1));
  49. $firstCheckTime = apply_filters(
  50. $this->updateChecker->getUniqueName('first_check_time'),
  51. $firstCheckTime
  52. );
  53. wp_schedule_event($firstCheckTime, $scheduleName, $this->cronHook);
  54. }
  55. add_action($this->cronHook, array($this, 'maybeCheckForUpdates'));
  56. //In case Cron is disabled or unreliable, we also manually trigger
  57. //the periodic checks while the user is browsing the Dashboard.
  58. add_action( 'admin_init', array($this, 'maybeCheckForUpdates') );
  59. //Like WordPress itself, we check more often on certain pages.
  60. /** @see wp_update_plugins */
  61. add_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));
  62. //"load-update.php" and "load-plugins.php" or "load-themes.php".
  63. $this->hourlyCheckHooks = array_merge($this->hourlyCheckHooks, $hourlyHooks);
  64. foreach($this->hourlyCheckHooks as $hook) {
  65. add_action($hook, array($this, 'maybeCheckForUpdates'));
  66. }
  67. //This hook fires after a bulk update is complete.
  68. add_action('upgrader_process_complete', array($this, 'upgraderProcessComplete'), 11, 2);
  69. } else {
  70. //Periodic checks are disabled.
  71. wp_clear_scheduled_hook($this->cronHook);
  72. }
  73. }
  74. /**
  75. * Runs upon the WP action upgrader_process_complete.
  76. *
  77. * We look at the parameters to decide whether to call maybeCheckForUpdates() or not.
  78. * We also check if the update checker has been removed by the update.
  79. *
  80. * @param WP_Upgrader $upgrader WP_Upgrader instance
  81. * @param array $upgradeInfo extra information about the upgrade
  82. */
  83. public function upgraderProcessComplete(
  84. /** @noinspection PhpUnusedParameterInspection */
  85. $upgrader, $upgradeInfo
  86. ) {
  87. //Cancel all further actions if the current version of PUC has been deleted or overwritten
  88. //by a different version during the upgrade. If we try to do anything more in that situation,
  89. //we could trigger a fatal error by trying to autoload a deleted class.
  90. clearstatcache();
  91. if ( !file_exists(__FILE__) ) {
  92. $this->removeHooks();
  93. $this->updateChecker->removeHooks();
  94. return;
  95. }
  96. //Sanity check and limitation to relevant types.
  97. if (
  98. !is_array($upgradeInfo) || !isset($upgradeInfo['type'], $upgradeInfo['action'])
  99. || 'update' !== $upgradeInfo['action'] || !in_array($upgradeInfo['type'], array('plugin', 'theme'))
  100. ) {
  101. return;
  102. }
  103. //Filter out notifications of upgrades that should have no bearing upon whether or not our
  104. //current info is up-to-date.
  105. if ( is_a($this->updateChecker, 'Puc_v4p11_Theme_UpdateChecker') ) {
  106. if ( 'theme' !== $upgradeInfo['type'] || !isset($upgradeInfo['themes']) ) {
  107. return;
  108. }
  109. //Letting too many things going through for checks is not a real problem, so we compare widely.
  110. if ( !in_array(
  111. strtolower($this->updateChecker->directoryName),
  112. array_map('strtolower', $upgradeInfo['themes'])
  113. ) ) {
  114. return;
  115. }
  116. }
  117. if ( is_a($this->updateChecker, 'Puc_v4p11_Plugin_UpdateChecker') ) {
  118. if ( 'plugin' !== $upgradeInfo['type'] || !isset($upgradeInfo['plugins']) ) {
  119. return;
  120. }
  121. //Themes pass in directory names in the information array, but plugins use the relative plugin path.
  122. if ( !in_array(
  123. strtolower($this->updateChecker->directoryName),
  124. array_map('dirname', array_map('strtolower', $upgradeInfo['plugins']))
  125. ) ) {
  126. return;
  127. }
  128. }
  129. $this->maybeCheckForUpdates();
  130. }
  131. /**
  132. * Check for updates if the configured check interval has already elapsed.
  133. * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron.
  134. *
  135. * You can override the default behaviour by using the "puc_check_now-$slug" filter.
  136. * The filter callback will be passed three parameters:
  137. * - Current decision. TRUE = check updates now, FALSE = don't check now.
  138. * - Last check time as a Unix timestamp.
  139. * - Configured check period in hours.
  140. * Return TRUE to check for updates immediately, or FALSE to cancel.
  141. *
  142. * This method is declared public because it's a hook callback. Calling it directly is not recommended.
  143. */
  144. public function maybeCheckForUpdates() {
  145. if ( empty($this->checkPeriod) ){
  146. return;
  147. }
  148. $state = $this->updateChecker->getUpdateState();
  149. $shouldCheck = ($state->timeSinceLastCheck() >= $this->getEffectiveCheckPeriod());
  150. //Let plugin authors substitute their own algorithm.
  151. $shouldCheck = apply_filters(
  152. $this->updateChecker->getUniqueName('check_now'),
  153. $shouldCheck,
  154. $state->getLastCheck(),
  155. $this->checkPeriod
  156. );
  157. if ( $shouldCheck ) {
  158. $this->updateChecker->checkForUpdates();
  159. }
  160. }
  161. /**
  162. * Calculate the actual check period based on the current status and environment.
  163. *
  164. * @return int Check period in seconds.
  165. */
  166. protected function getEffectiveCheckPeriod() {
  167. $currentFilter = current_filter();
  168. if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) {
  169. //Check more often when the user visits "Dashboard -> Updates" or does a bulk update.
  170. $period = 60;
  171. } else if ( in_array($currentFilter, $this->hourlyCheckHooks) ) {
  172. //Also check more often on /wp-admin/update.php and the "Plugins" or "Themes" page.
  173. $period = 3600;
  174. } else if ( $this->throttleRedundantChecks && ($this->updateChecker->getUpdate() !== null) ) {
  175. //Check less frequently if it's already known that an update is available.
  176. $period = $this->throttledCheckPeriod * 3600;
  177. } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) {
  178. //WordPress cron schedules are not exact, so lets do an update check even
  179. //if slightly less than $checkPeriod hours have elapsed since the last check.
  180. $cronFuzziness = 20 * 60;
  181. $period = $this->checkPeriod * 3600 - $cronFuzziness;
  182. } else {
  183. $period = $this->checkPeriod * 3600;
  184. }
  185. return $period;
  186. }
  187. /**
  188. * Add our custom schedule to the array of Cron schedules used by WP.
  189. *
  190. * @param array $schedules
  191. * @return array
  192. */
  193. public function _addCustomSchedule($schedules) {
  194. if ( $this->checkPeriod && ($this->checkPeriod > 0) ){
  195. $scheduleName = 'every' . $this->checkPeriod . 'hours';
  196. $schedules[$scheduleName] = array(
  197. 'interval' => $this->checkPeriod * 3600,
  198. 'display' => sprintf('Every %d hours', $this->checkPeriod),
  199. );
  200. }
  201. return $schedules;
  202. }
  203. /**
  204. * Remove the scheduled cron event that the library uses to check for updates.
  205. *
  206. * @return void
  207. */
  208. public function removeUpdaterCron() {
  209. wp_clear_scheduled_hook($this->cronHook);
  210. }
  211. /**
  212. * Get the name of the update checker's WP-cron hook. Mostly useful for debugging.
  213. *
  214. * @return string
  215. */
  216. public function getCronHookName() {
  217. return $this->cronHook;
  218. }
  219. /**
  220. * Remove most hooks added by the scheduler.
  221. */
  222. public function removeHooks() {
  223. remove_filter('cron_schedules', array($this, '_addCustomSchedule'));
  224. remove_action('admin_init', array($this, 'maybeCheckForUpdates'));
  225. remove_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));
  226. if ( $this->cronHook !== null ) {
  227. remove_action($this->cronHook, array($this, 'maybeCheckForUpdates'));
  228. }
  229. if ( !empty($this->hourlyCheckHooks) ) {
  230. foreach ($this->hourlyCheckHooks as $hook) {
  231. remove_action($hook, array($this, 'maybeCheckForUpdates'));
  232. }
  233. }
  234. }
  235. }
  236. endif;