| Total Complexity | 43 |
| Total Lines | 404 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like UpdateChecker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use UpdateChecker, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 18 | class UpdateChecker extends BaseUpdateChecker { |
||
| 19 | protected $updateTransient = 'update_plugins'; |
||
| 20 | protected $translationType = 'plugin'; |
||
| 21 | |||
| 22 | public $pluginAbsolutePath = ''; //Full path of the main plugin file. |
||
| 23 | public $pluginFile = ''; //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins. |
||
| 24 | public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory. |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @var Package |
||
| 28 | */ |
||
| 29 | protected $package; |
||
| 30 | |||
| 31 | private $extraUi = null; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Class constructor. |
||
| 35 | * |
||
| 36 | * @param string $metadataUrl The URL of the plugin's metadata file. |
||
| 37 | * @param string $pluginFile Fully qualified path to the main plugin file. |
||
| 38 | * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug. |
||
| 39 | * @param integer $checkPeriod How often to check for updates (in hours). Defaults to checking every 12 hours. Set to 0 to disable automatic update checks. |
||
| 40 | * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'. |
||
| 41 | * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory. |
||
| 42 | */ |
||
| 43 | public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){ |
||
| 44 | $this->pluginAbsolutePath = $pluginFile; |
||
| 45 | $this->pluginFile = plugin_basename($this->pluginAbsolutePath); |
||
| 46 | $this->muPluginFile = $muPluginFile; |
||
| 47 | |||
| 48 | //If no slug is specified, use the name of the main plugin file as the slug. |
||
| 49 | //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'. |
||
| 50 | if ( empty($slug) ){ |
||
| 51 | $slug = basename($this->pluginFile, '.php'); |
||
| 52 | } |
||
| 53 | |||
| 54 | //Plugin slugs must be unique. |
||
| 55 | $slugCheckFilter = 'puc_is_slug_in_use-' . $slug; |
||
| 56 | $slugUsedBy = apply_filters($slugCheckFilter, false); |
||
| 57 | if ( $slugUsedBy ) { |
||
| 58 | $this->triggerError(sprintf( |
||
| 59 | 'Plugin slug "%s" is already in use by %s. Slugs must be unique.', |
||
| 60 | $slug, |
||
| 61 | $slugUsedBy |
||
| 62 | ), E_USER_ERROR); |
||
| 63 | } |
||
| 64 | add_filter($slugCheckFilter, array($this, 'getAbsolutePath')); |
||
| 65 | |||
| 66 | parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName); |
||
| 67 | |||
| 68 | //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume |
||
| 69 | //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir). |
||
| 70 | if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) { |
||
| 71 | $this->muPluginFile = $this->pluginFile; |
||
| 72 | } |
||
| 73 | |||
| 74 | //To prevent a crash during plugin uninstallation, remove updater hooks when the user removes the plugin. |
||
| 75 | //Details: https://github.com/YahnisElsts/plugin-update-checker/issues/138#issuecomment-335590964 |
||
| 76 | add_action('uninstall_' . $this->pluginFile, array($this, 'removeHooks')); |
||
| 77 | |||
| 78 | $this->extraUi = new Ui($this); |
||
| 79 | } |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Create an instance of the scheduler. |
||
| 83 | * |
||
| 84 | * @param int $checkPeriod |
||
| 85 | * @return Scheduler |
||
| 86 | */ |
||
| 87 | protected function createScheduler($checkPeriod) { |
||
| 88 | $scheduler = new Scheduler($this, $checkPeriod, array('load-plugins.php')); |
||
| 89 | register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron')); |
||
| 90 | return $scheduler; |
||
| 91 | } |
||
| 92 | |||
| 93 | /** |
||
| 94 | * Install the hooks required to run periodic update checks and inject update info |
||
| 95 | * into WP data structures. |
||
| 96 | * |
||
| 97 | * @return void |
||
| 98 | */ |
||
| 99 | protected function installHooks(){ |
||
| 100 | //Override requests for plugin information |
||
| 101 | add_filter('plugins_api', array($this, 'injectInfo'), 20, 3); |
||
| 102 | |||
| 103 | parent::installHooks(); |
||
| 104 | } |
||
| 105 | |||
| 106 | /** |
||
| 107 | * Remove update checker hooks. |
||
| 108 | * |
||
| 109 | * The intent is to prevent a fatal error that can happen if the plugin has an uninstall |
||
| 110 | * hook. During uninstallation, WP includes the main plugin file (which creates a PUC instance), |
||
| 111 | * the uninstall hook runs, WP deletes the plugin files and then updates some transients. |
||
| 112 | * If PUC hooks are still around at this time, they could throw an error while trying to |
||
| 113 | * autoload classes from files that no longer exist. |
||
| 114 | * |
||
| 115 | * The "site_transient_{$transient}" filter is the main problem here, but let's also remove |
||
| 116 | * most other PUC hooks to be safe. |
||
| 117 | * |
||
| 118 | * @internal |
||
| 119 | */ |
||
| 120 | public function removeHooks() { |
||
| 121 | parent::removeHooks(); |
||
| 122 | $this->extraUi->removeHooks(); |
||
| 123 | $this->package->removeHooks(); |
||
| 124 | |||
| 125 | remove_filter('plugins_api', array($this, 'injectInfo'), 20); |
||
| 126 | } |
||
| 127 | |||
| 128 | /** |
||
| 129 | * Retrieve plugin info from the configured API endpoint. |
||
| 130 | * |
||
| 131 | * @uses wp_remote_get() |
||
| 132 | * |
||
| 133 | * @param array $queryArgs Additional query arguments to append to the request. Optional. |
||
| 134 | * @return PluginInfo |
||
| 135 | */ |
||
| 136 | public function requestInfo($queryArgs = array()) { |
||
| 137 | list($pluginInfo, $result) = $this->requestMetadata( |
||
| 138 | PluginInfo::class, |
||
| 139 | 'request_info', |
||
| 140 | $queryArgs |
||
| 141 | ); |
||
| 142 | |||
| 143 | if ( $pluginInfo !== null ) { |
||
| 144 | /** @var PluginInfo $pluginInfo */ |
||
| 145 | $pluginInfo->filename = $this->pluginFile; |
||
| 146 | $pluginInfo->slug = $this->slug; |
||
| 147 | } |
||
| 148 | |||
| 149 | $pluginInfo = apply_filters($this->getUniqueName('request_info_result'), $pluginInfo, $result); |
||
| 150 | return $pluginInfo; |
||
| 151 | } |
||
| 152 | |||
| 153 | /** |
||
| 154 | * Retrieve the latest update (if any) from the configured API endpoint. |
||
| 155 | * |
||
| 156 | * @uses UpdateChecker::requestInfo() |
||
| 157 | * |
||
| 158 | * @return Update|null An instance of Plugin Update, or NULL when no updates are available. |
||
| 159 | */ |
||
| 160 | public function requestUpdate() { |
||
| 161 | //For the sake of simplicity, this function just calls requestInfo() |
||
| 162 | //and transforms the result accordingly. |
||
| 163 | $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1')); |
||
| 164 | if ( $pluginInfo === null ){ |
||
| 165 | return null; |
||
| 166 | } |
||
| 167 | $update = Update::fromPluginInfo($pluginInfo); |
||
| 168 | |||
| 169 | $update = $this->filterUpdateResult($update); |
||
| 170 | |||
| 171 | return $update; |
||
| 172 | } |
||
| 173 | |||
| 174 | /** |
||
| 175 | * Intercept plugins_api() calls that request information about our plugin and |
||
| 176 | * use the configured API endpoint to satisfy them. |
||
| 177 | * |
||
| 178 | * @see plugins_api() |
||
| 179 | * |
||
| 180 | * @param mixed $result |
||
| 181 | * @param string $action |
||
| 182 | * @param array|object $args |
||
| 183 | * @return mixed |
||
| 184 | */ |
||
| 185 | public function injectInfo($result, $action = null, $args = null){ |
||
| 186 | $relevant = ($action == 'plugin_information') && isset($args->slug) && ( |
||
| 187 | ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile)) |
||
| 188 | ); |
||
| 189 | if ( !$relevant ) { |
||
| 190 | return $result; |
||
| 191 | } |
||
| 192 | |||
| 193 | $pluginInfo = $this->requestInfo(); |
||
| 194 | $this->fixSupportedWordpressVersion($pluginInfo); |
||
| 195 | |||
| 196 | $pluginInfo = apply_filters($this->getUniqueName('pre_inject_info'), $pluginInfo); |
||
| 197 | if ( $pluginInfo ) { |
||
| 198 | return $pluginInfo->toWpFormat(); |
||
| 199 | } |
||
| 200 | |||
| 201 | return $result; |
||
| 202 | } |
||
| 203 | |||
| 204 | protected function shouldShowUpdates() { |
||
| 205 | //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file |
||
| 206 | //is usually different from the main plugin file so the update wouldn't show up properly anyway. |
||
| 207 | return !$this->isUnknownMuPlugin(); |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * @param \stdClass|null $updates |
||
| 212 | * @param \stdClass $updateToAdd |
||
| 213 | * @return \stdClass |
||
| 214 | */ |
||
| 215 | protected function addUpdateToList($updates, $updateToAdd) { |
||
| 222 | } |
||
| 223 | |||
| 224 | /** |
||
| 225 | * @param \stdClass|null $updates |
||
| 226 | * @return \stdClass|null |
||
| 227 | */ |
||
| 228 | protected function removeUpdateFromList($updates) { |
||
| 229 | $updates = parent::removeUpdateFromList($updates); |
||
| 230 | if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) { |
||
| 231 | unset($updates->response[$this->muPluginFile]); |
||
| 232 | } |
||
| 233 | return $updates; |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * For plugins, the update array is indexed by the plugin filename relative to the "plugins" |
||
| 238 | * directory. Example: "plugin-name/plugin.php". |
||
| 239 | * |
||
| 240 | * @return string |
||
| 241 | */ |
||
| 242 | protected function getUpdateListKey() { |
||
| 243 | if ( $this->package->isMuPlugin() ) { |
||
| 244 | return $this->muPluginFile; |
||
| 245 | } |
||
| 246 | return $this->pluginFile; |
||
| 247 | } |
||
| 248 | |||
| 249 | protected function getNoUpdateItemFields() { |
||
| 250 | return array_merge( |
||
| 251 | parent::getNoUpdateItemFields(), |
||
| 252 | array( |
||
| 253 | 'id' => $this->pluginFile, |
||
| 254 | 'slug' => $this->slug, |
||
| 255 | 'plugin' => $this->pluginFile, |
||
| 256 | 'icons' => array(), |
||
| 257 | 'banners' => array(), |
||
| 258 | 'banners_rtl' => array(), |
||
| 259 | 'tested' => '', |
||
| 260 | 'compatibility' => new \stdClass(), |
||
| 261 | ) |
||
| 262 | ); |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * Alias for isBeingUpgraded(). |
||
| 267 | * |
||
| 268 | * @deprecated |
||
| 269 | * @param \WP_Upgrader|null $upgrader The upgrader that's performing the current update. |
||
| 270 | * @return bool |
||
| 271 | */ |
||
| 272 | public function isPluginBeingUpgraded($upgrader = null) { |
||
| 273 | return $this->isBeingUpgraded($upgrader); |
||
| 274 | } |
||
| 275 | |||
| 276 | /** |
||
| 277 | * Is there an update being installed for this plugin, right now? |
||
| 278 | * |
||
| 279 | * @param \WP_Upgrader|null $upgrader |
||
| 280 | * @return bool |
||
| 281 | */ |
||
| 282 | public function isBeingUpgraded($upgrader = null) { |
||
| 283 | return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader); |
||
| 284 | } |
||
| 285 | |||
| 286 | /** |
||
| 287 | * Get the details of the currently available update, if any. |
||
| 288 | * |
||
| 289 | * If no updates are available, or if the last known update version is below or equal |
||
| 290 | * to the currently installed version, this method will return NULL. |
||
| 291 | * |
||
| 292 | * Uses cached update data. To retrieve update information straight from |
||
| 293 | * the metadata URL, call requestUpdate() instead. |
||
| 294 | * |
||
| 295 | * @return Update|null |
||
| 296 | */ |
||
| 297 | public function getUpdate() { |
||
| 304 | } |
||
| 305 | |||
| 306 | /** |
||
| 307 | * Get the translated plugin title. |
||
| 308 | * |
||
| 309 | * @deprecated |
||
| 310 | * @return string |
||
| 311 | */ |
||
| 312 | public function getPluginTitle() { |
||
| 313 | return $this->package->getPluginTitle(); |
||
| 314 | } |
||
| 315 | |||
| 316 | /** |
||
| 317 | * Check if the current user has the required permissions to install updates. |
||
| 318 | * |
||
| 319 | * @return bool |
||
| 320 | */ |
||
| 321 | public function userCanInstallUpdates() { |
||
| 322 | return current_user_can('update_plugins'); |
||
| 323 | } |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Check if the plugin file is inside the mu-plugins directory. |
||
| 327 | * |
||
| 328 | * @deprecated |
||
| 329 | * @return bool |
||
| 330 | */ |
||
| 331 | protected function isMuPlugin() { |
||
| 332 | return $this->package->isMuPlugin(); |
||
| 333 | } |
||
| 334 | |||
| 335 | /** |
||
| 336 | * MU plugins are partially supported, but only when we know which file in mu-plugins |
||
| 337 | * corresponds to this plugin. |
||
| 338 | * |
||
| 339 | * @return bool |
||
| 340 | */ |
||
| 341 | protected function isUnknownMuPlugin() { |
||
| 342 | return empty($this->muPluginFile) && $this->package->isMuPlugin(); |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Get absolute path to the main plugin file. |
||
| 347 | * |
||
| 348 | * @return string |
||
| 349 | */ |
||
| 350 | public function getAbsolutePath() { |
||
| 351 | return $this->pluginAbsolutePath; |
||
| 352 | } |
||
| 353 | |||
| 354 | /** |
||
| 355 | * Register a callback for filtering query arguments. |
||
| 356 | * |
||
| 357 | * The callback function should take one argument - an associative array of query arguments. |
||
| 358 | * It should return a modified array of query arguments. |
||
| 359 | * |
||
| 360 | * @uses add_filter() This method is a convenience wrapper for add_filter(). |
||
| 361 | * |
||
| 362 | * @param callable $callback |
||
| 363 | * @return void |
||
| 364 | */ |
||
| 365 | public function addQueryArgFilter($callback){ |
||
| 366 | $this->addFilter('request_info_query_args', $callback); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * Register a callback for filtering arguments passed to wp_remote_get(). |
||
| 371 | * |
||
| 372 | * The callback function should take one argument - an associative array of arguments - |
||
| 373 | * and return a modified array or arguments. See the WP documentation on wp_remote_get() |
||
| 374 | * for details on what arguments are available and how they work. |
||
| 375 | * |
||
| 376 | * @uses add_filter() This method is a convenience wrapper for add_filter(). |
||
| 377 | * |
||
| 378 | * @param callable $callback |
||
| 379 | * @return void |
||
| 380 | */ |
||
| 381 | public function addHttpRequestArgFilter($callback) { |
||
| 382 | $this->addFilter('request_info_options', $callback); |
||
| 383 | } |
||
| 384 | |||
| 385 | /** |
||
| 386 | * Register a callback for filtering the plugin info retrieved from the external API. |
||
| 387 | * |
||
| 388 | * The callback function should take two arguments. If the plugin info was retrieved |
||
| 389 | * successfully, the first argument passed will be an instance of PluginInfo. Otherwise, |
||
| 390 | * it will be NULL. The second argument will be the corresponding return value of |
||
| 391 | * wp_remote_get (see WP docs for details). |
||
| 392 | * |
||
| 393 | * The callback function should return a new or modified instance of PluginInfo or NULL. |
||
| 394 | * |
||
| 395 | * @uses add_filter() This method is a convenience wrapper for add_filter(). |
||
| 396 | * |
||
| 397 | * @param callable $callback |
||
| 398 | * @return void |
||
| 399 | */ |
||
| 400 | public function addResultFilter($callback) { |
||
| 402 | } |
||
| 403 | |||
| 404 | protected function createDebugBarExtension() { |
||
| 405 | return new DebugBar\PluginExtension($this); |
||
| 406 | } |
||
| 407 | |||
| 408 | /** |
||
| 409 | * Create a package instance that represents this plugin or theme. |
||
| 410 | * |
||
| 411 | * @return InstalledPackage |
||
| 412 | */ |
||
| 413 | protected function createInstalledPackage() { |
||
| 415 | } |
||
| 416 | |||
| 417 | /** |
||
| 418 | * @return Package |
||
| 419 | */ |
||
| 420 | public function getInstalledPackage() { |
||
| 421 | return $this->package; |
||
| 422 | } |
||
| 423 | } |
||
| 424 | |||
| 425 | endif; |
||
| 426 |