123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- <?php
- namespace YahnisElsts\PluginUpdateChecker\v5p1\Plugin;
- use YahnisElsts\PluginUpdateChecker\v5p1\InstalledPackage;
- use YahnisElsts\PluginUpdateChecker\v5p1\UpdateChecker as BaseUpdateChecker;
- use YahnisElsts\PluginUpdateChecker\v5p1\Scheduler;
- use YahnisElsts\PluginUpdateChecker\v5p1\DebugBar;
- if ( !class_exists(UpdateChecker::class, false) ):
- /**
- * A custom plugin update checker.
- *
- * @author Janis Elsts
- * @copyright 2018
- * @access public
- */
- class UpdateChecker extends BaseUpdateChecker {
- protected $updateTransient = 'update_plugins';
- protected $componentType = 'plugin';
- 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 $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
- /**
- * @var Package
- */
- protected $package;
- private $extraUi = null;
- /**
- * Class constructor.
- *
- * @param string $metadataUrl The URL of the plugin's metadata file.
- * @param string $pluginFile Fully qualified path to the main plugin file.
- * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
- * @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.
- * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
- * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
- */
- public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
- $this->pluginAbsolutePath = $pluginFile;
- $this->pluginFile = plugin_basename($this->pluginAbsolutePath);
- $this->muPluginFile = $muPluginFile;
- //If no slug is specified, use the name of the main plugin file as the slug.
- //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
- if ( empty($slug) ){
- $slug = basename($this->pluginFile, '.php');
- }
- //Plugin slugs must be unique.
- $slugCheckFilter = 'puc_is_slug_in_use-' . $slug;
- $slugUsedBy = apply_filters($slugCheckFilter, false);
- if ( $slugUsedBy ) {
- $this->triggerError(sprintf(
- 'Plugin slug "%s" is already in use by %s. Slugs must be unique.',
- $slug,
- $slugUsedBy
- ), E_USER_ERROR);
- }
- add_filter($slugCheckFilter, array($this, 'getAbsolutePath'));
- parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName);
- //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
- //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
- if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {
- $this->muPluginFile = $this->pluginFile;
- }
- //To prevent a crash during plugin uninstallation, remove updater hooks when the user removes the plugin.
- //Details: https://github.com/YahnisElsts/plugin-update-checker/issues/138#issuecomment-335590964
- add_action('uninstall_' . $this->pluginFile, array($this, 'removeHooks'));
- $this->extraUi = new Ui($this);
- }
- /**
- * Create an instance of the scheduler.
- *
- * @param int $checkPeriod
- * @return Scheduler
- */
- protected function createScheduler($checkPeriod) {
- $scheduler = new Scheduler($this, $checkPeriod, array('load-plugins.php'));
- register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron'));
- return $scheduler;
- }
- /**
- * Install the hooks required to run periodic update checks and inject update info
- * into WP data structures.
- *
- * @return void
- */
- protected function installHooks(){
- //Override requests for plugin information
- add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
- parent::installHooks();
- }
- /**
- * Remove update checker hooks.
- *
- * The intent is to prevent a fatal error that can happen if the plugin has an uninstall
- * hook. During uninstallation, WP includes the main plugin file (which creates a PUC instance),
- * the uninstall hook runs, WP deletes the plugin files and then updates some transients.
- * If PUC hooks are still around at this time, they could throw an error while trying to
- * autoload classes from files that no longer exist.
- *
- * The "site_transient_{$transient}" filter is the main problem here, but let's also remove
- * most other PUC hooks to be safe.
- *
- * @internal
- */
- public function removeHooks() {
- parent::removeHooks();
- $this->extraUi->removeHooks();
- $this->package->removeHooks();
- remove_filter('plugins_api', array($this, 'injectInfo'), 20);
- }
- /**
- * Retrieve plugin info from the configured API endpoint.
- *
- * @uses wp_remote_get()
- *
- * @param array $queryArgs Additional query arguments to append to the request. Optional.
- * @return PluginInfo
- */
- public function requestInfo($queryArgs = array()) {
- list($pluginInfo, $result) = $this->requestMetadata(
- PluginInfo::class,
- 'request_info',
- $queryArgs
- );
- if ( $pluginInfo !== null ) {
- /** @var PluginInfo $pluginInfo */
- $pluginInfo->filename = $this->pluginFile;
- $pluginInfo->slug = $this->slug;
- }
- $pluginInfo = apply_filters($this->getUniqueName('request_info_result'), $pluginInfo, $result);
- return $pluginInfo;
- }
- /**
- * Retrieve the latest update (if any) from the configured API endpoint.
- *
- * @uses UpdateChecker::requestInfo()
- *
- * @return Update|null An instance of Plugin Update, or NULL when no updates are available.
- */
- public function requestUpdate() {
- //For the sake of simplicity, this function just calls requestInfo()
- //and transforms the result accordingly.
- $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
- if ( $pluginInfo === null ){
- return null;
- }
- $update = Update::fromPluginInfo($pluginInfo);
- $update = $this->filterUpdateResult($update);
- return $update;
- }
- /**
- * Intercept plugins_api() calls that request information about our plugin and
- * use the configured API endpoint to satisfy them.
- *
- * @see plugins_api()
- *
- * @param mixed $result
- * @param string $action
- * @param array|object $args
- * @return mixed
- */
- public function injectInfo($result, $action = null, $args = null){
- $relevant = ($action == 'plugin_information') && isset($args->slug) && (
- ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
- );
- if ( !$relevant ) {
- return $result;
- }
- $pluginInfo = $this->requestInfo();
- $this->fixSupportedWordpressVersion($pluginInfo);
- $pluginInfo = apply_filters($this->getUniqueName('pre_inject_info'), $pluginInfo);
- if ( $pluginInfo ) {
- return $pluginInfo->toWpFormat();
- }
- return $result;
- }
- protected function shouldShowUpdates() {
- //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
- //is usually different from the main plugin file so the update wouldn't show up properly anyway.
- return !$this->isUnknownMuPlugin();
- }
- /**
- * @param \stdClass|null $updates
- * @param \stdClass $updateToAdd
- * @return \stdClass
- */
- protected function addUpdateToList($updates, $updateToAdd) {
- if ( $this->package->isMuPlugin() ) {
- //WP does not support automatic update installation for mu-plugins, but we can
- //still display a notice.
- $updateToAdd->package = null;
- }
- return parent::addUpdateToList($updates, $updateToAdd);
- }
- /**
- * @param \stdClass|null $updates
- * @return \stdClass|null
- */
- protected function removeUpdateFromList($updates) {
- $updates = parent::removeUpdateFromList($updates);
- if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) {
- unset($updates->response[$this->muPluginFile]);
- }
- return $updates;
- }
- /**
- * For plugins, the update array is indexed by the plugin filename relative to the "plugins"
- * directory. Example: "plugin-name/plugin.php".
- *
- * @return string
- */
- protected function getUpdateListKey() {
- if ( $this->package->isMuPlugin() ) {
- return $this->muPluginFile;
- }
- return $this->pluginFile;
- }
- protected function getNoUpdateItemFields() {
- return array_merge(
- parent::getNoUpdateItemFields(),
- array(
- 'id' => $this->pluginFile,
- 'slug' => $this->slug,
- 'plugin' => $this->pluginFile,
- 'icons' => array(),
- 'banners' => array(),
- 'banners_rtl' => array(),
- 'tested' => '',
- 'compatibility' => new \stdClass(),
- )
- );
- }
- /**
- * Alias for isBeingUpgraded().
- *
- * @deprecated
- * @param \WP_Upgrader|null $upgrader The upgrader that's performing the current update.
- * @return bool
- */
- public function isPluginBeingUpgraded($upgrader = null) {
- return $this->isBeingUpgraded($upgrader);
- }
- /**
- * Is there an update being installed for this plugin, right now?
- *
- * @param \WP_Upgrader|null $upgrader
- * @return bool
- */
- public function isBeingUpgraded($upgrader = null) {
- return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);
- }
- /**
- * Get the details of the currently available update, if any.
- *
- * If no updates are available, or if the last known update version is below or equal
- * to the currently installed version, this method will return NULL.
- *
- * Uses cached update data. To retrieve update information straight from
- * the metadata URL, call requestUpdate() instead.
- *
- * @return Update|null
- */
- public function getUpdate() {
- $update = parent::getUpdate();
- if ( isset($update) ) {
- /** @var Update $update */
- $update->filename = $this->pluginFile;
- }
- return $update;
- }
- /**
- * Get the translated plugin title.
- *
- * @deprecated
- * @return string
- */
- public function getPluginTitle() {
- return $this->package->getPluginTitle();
- }
- /**
- * Check if the current user has the required permissions to install updates.
- *
- * @return bool
- */
- public function userCanInstallUpdates() {
- return current_user_can('update_plugins');
- }
- /**
- * Check if the plugin file is inside the mu-plugins directory.
- *
- * @deprecated
- * @return bool
- */
- protected function isMuPlugin() {
- return $this->package->isMuPlugin();
- }
- /**
- * MU plugins are partially supported, but only when we know which file in mu-plugins
- * corresponds to this plugin.
- *
- * @return bool
- */
- protected function isUnknownMuPlugin() {
- return empty($this->muPluginFile) && $this->package->isMuPlugin();
- }
- /**
- * Get absolute path to the main plugin file.
- *
- * @return string
- */
- public function getAbsolutePath() {
- return $this->pluginAbsolutePath;
- }
- /**
- * Register a callback for filtering query arguments.
- *
- * The callback function should take one argument - an associative array of query arguments.
- * It should return a modified array of query arguments.
- *
- * @uses add_filter() This method is a convenience wrapper for add_filter().
- *
- * @param callable $callback
- * @return void
- */
- public function addQueryArgFilter($callback){
- $this->addFilter('request_info_query_args', $callback);
- }
- /**
- * Register a callback for filtering arguments passed to wp_remote_get().
- *
- * The callback function should take one argument - an associative array of arguments -
- * and return a modified array or arguments. See the WP documentation on wp_remote_get()
- * for details on what arguments are available and how they work.
- *
- * @uses add_filter() This method is a convenience wrapper for add_filter().
- *
- * @param callable $callback
- * @return void
- */
- public function addHttpRequestArgFilter($callback) {
- $this->addFilter('request_info_options', $callback);
- }
- /**
- * Register a callback for filtering the plugin info retrieved from the external API.
- *
- * The callback function should take two arguments. If the plugin info was retrieved
- * successfully, the first argument passed will be an instance of PluginInfo. Otherwise,
- * it will be NULL. The second argument will be the corresponding return value of
- * wp_remote_get (see WP docs for details).
- *
- * The callback function should return a new or modified instance of PluginInfo or NULL.
- *
- * @uses add_filter() This method is a convenience wrapper for add_filter().
- *
- * @param callable $callback
- * @return void
- */
- public function addResultFilter($callback) {
- $this->addFilter('request_info_result', $callback, 10, 2);
- }
- protected function createDebugBarExtension() {
- return new DebugBar\PluginExtension($this);
- }
- /**
- * Create a package instance that represents this plugin or theme.
- *
- * @return InstalledPackage
- */
- protected function createInstalledPackage() {
- return new Package($this->pluginAbsolutePath, $this);
- }
- /**
- * @return Package
- */
- public function getInstalledPackage() {
- return $this->package;
- }
- }
- endif;
|