333 lines
9.4 KiB
PHP
333 lines
9.4 KiB
PHP
<?php
|
|
namespace AIOSEO\Plugin\Pro\Admin;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* The updates class to check for updates from our server.
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
class Updates {
|
|
use \AIOSEO\Plugin\Pro\Traits\Updates;
|
|
|
|
/**
|
|
* Plugin slug.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var bool|string
|
|
*/
|
|
public $pluginSlug = false;
|
|
|
|
/**
|
|
* Plugin path.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var bool|string
|
|
*/
|
|
public $pluginPath = false;
|
|
|
|
/**
|
|
* Version number of the plugin.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var bool|int
|
|
*/
|
|
public $version = false;
|
|
|
|
/**
|
|
* License key for the plugin.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var bool|string
|
|
*/
|
|
public $key = false;
|
|
|
|
/**
|
|
* Store the update data returned from the API.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var object
|
|
*/
|
|
public $update;
|
|
|
|
/**
|
|
* Store the plugin info details for the update.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var bool|object
|
|
*/
|
|
public $info = false;
|
|
|
|
/**
|
|
* Source of notifications content.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
public $baseUrl = 'https://licensing.aioseo.com/v1/';
|
|
|
|
/**
|
|
* Primary class constructor.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param array $config Array of updater config args.
|
|
*/
|
|
public function __construct( array $config ) {
|
|
// Set class properties.
|
|
$acceptedArgs = [
|
|
'pluginSlug',
|
|
'pluginPath',
|
|
'version',
|
|
'key',
|
|
];
|
|
|
|
foreach ( $acceptedArgs as $arg ) {
|
|
$this->$arg = $config[ $arg ];
|
|
}
|
|
|
|
// Initialize the class on "init". This cannot run on "plugins_loaded"
|
|
// because that's when this class is constructed in the first place.
|
|
add_action( 'init', [ $this, 'init' ] );
|
|
}
|
|
|
|
/**
|
|
* Initialize the class.
|
|
*
|
|
* @since 4.7.0
|
|
*/
|
|
public function init() {
|
|
// If the current user cannot update plugins, stop processing here.
|
|
// We want to make sure a user is logged in because we need WP CLI and cron jobs to get past this check.
|
|
if ( is_user_logged_in() && ! current_user_can( 'update_plugins' ) ) {
|
|
return;
|
|
}
|
|
|
|
// Load the updater hooks and filters.
|
|
add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'updatePluginsFilter' ], 1000 );
|
|
add_filter( 'plugins_api', [ $this, 'pluginsApi' ], 10, 3 );
|
|
|
|
if ( ! wp_doing_cron() ) {
|
|
add_filter( 'upgrader_package_options', [ $this, 'validateDownloadUrl' ] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add our Pro plugin update details when WordPress runs its update checker.
|
|
* Right before WordPress saves the plugin update object, we infuse it with our own data.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param mixed $value The WordPress update object.
|
|
* @return object Amended WordPress update object on success, default if object is empty.
|
|
*/
|
|
public function updatePluginsFilter( $value ) {
|
|
// If no update object exists, bail to prevent errors.
|
|
if ( empty( $value ) || ! is_object( $value ) ) {
|
|
return $value;
|
|
}
|
|
|
|
// If we haven't checked for update details, do so now.
|
|
// wp_update_plugins() sets the transient twice so we store the update to prevent a second redundant request.
|
|
// If the request fails, we will return a default object based on the current version of the plugin.
|
|
if ( empty( $this->update ) ) {
|
|
$this->update = $this->checkForUpdates();
|
|
|
|
if ( ! is_object( $this->update ) || ! empty( $this->update->error ) ) {
|
|
$this->update = new \stdClass();
|
|
|
|
return $value;
|
|
}
|
|
|
|
$this->update->description = $this->update->description ? preg_replace( '/\s+/', ' ', (string) $this->update->description ) : null;
|
|
$this->update->changelog = $this->update->changelog ? preg_replace( '/\s+/', ' ', (string) $this->update->changelog ) : null;
|
|
}
|
|
|
|
$this->update->icons = ! empty( $this->update->icons ) ? (array) $this->update->icons : [];
|
|
$this->update->aioseo = true;
|
|
$this->update->plugin = $this->pluginPath;
|
|
$this->update->oldVersion = $this->version;
|
|
|
|
// Infuse the update object with our data if the version from the remote API is newer.
|
|
if ( isset( $this->update->new_version ) && version_compare( $this->version, $this->update->new_version, '<' ) ) {
|
|
// The $plugin_update object contains new_version, package, slug, and last_update keys.
|
|
$value->response[ $this->pluginPath ] = $this->update;
|
|
} else {
|
|
$this->update->new_version = $this->version;
|
|
$this->update->plugin = $this->pluginPath;
|
|
$value->no_update[ $this->pluginPath ] = $this->update;
|
|
}
|
|
|
|
// Return the update object.
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Check for updates request.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return mixed The update object, or null if the request fails.
|
|
*/
|
|
public function checkForUpdates() {
|
|
$cacheKeyHash = sha1( $this->pluginSlug . $this->version . $this->key );
|
|
$cacheKey = "aioseo_update_check_{$cacheKeyHash}";
|
|
$cachedUpdate = aioseo()->core->networkCache->get( $cacheKey );
|
|
if ( null !== $cachedUpdate ) {
|
|
return $cachedUpdate;
|
|
}
|
|
|
|
$args = [
|
|
'license' => $this->key,
|
|
'domain' => aioseo()->helpers->getSiteDomain( true ),
|
|
'sku' => $this->pluginSlug,
|
|
'version' => $this->version,
|
|
'php_version' => PHP_VERSION,
|
|
'wp_version' => get_bloginfo( 'version' )
|
|
];
|
|
|
|
$response = aioseo()->helpers->wpRemotePost( $this->getUrl() . 'update/', [
|
|
'timeout' => 30,
|
|
'body' => wp_json_encode( $args )
|
|
] );
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
return null;
|
|
}
|
|
|
|
$update = json_decode( wp_remote_retrieve_body( $response ) );
|
|
|
|
// Validate the response has required properties
|
|
$isValid = ! empty( $update ) &&
|
|
is_object( $update ) &&
|
|
! property_exists( $update, 'error' ) &&
|
|
property_exists( $update, 'new_version' );
|
|
|
|
// Cache for 1 hour if valid, 10 minutes if invalid.
|
|
$cacheDuration = $isValid ? HOUR_IN_SECONDS : 10 * MINUTE_IN_SECONDS;
|
|
aioseo()->core->networkCache->update( $cacheKey, $update, $cacheDuration );
|
|
|
|
return $update;
|
|
}
|
|
|
|
/**
|
|
* Filter the plugins_api function to get our own custom plugin information
|
|
* from our private repo.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param object $api The original plugins_api object.
|
|
* @param string $action The action sent by plugins_api.
|
|
* @param object $args Additional args to send to plugins_api.
|
|
* @return object New stdClass with plugin information on success, default response on failure.
|
|
*/
|
|
public function pluginsApi( $api, $action = '', $args = null ) {
|
|
$plugin = ( 'plugin_information' === $action ) && isset( $args->slug ) && ( $this->pluginSlug === $args->slug );
|
|
|
|
// If our plugin matches the request, set our own plugin data, else return the default response.
|
|
if ( $plugin ) {
|
|
return $this->setPluginsApi( $api );
|
|
}
|
|
|
|
return $api;
|
|
}
|
|
|
|
/**
|
|
* Ping a remote API to retrieve plugin information for WordPress to display.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param object $defaultApi The default API object.
|
|
* @return object Return custom plugin information to plugins_api.
|
|
*/
|
|
public function setPluginsApi( $defaultApi ) {
|
|
// Perform the remote request to retrieve our plugin information. If it fails, return the default object.
|
|
if ( ! $this->info ) {
|
|
$cacheKey = 'plugin_info_' . sha1( $this->pluginSlug . $this->version . $this->key );
|
|
$cachedInfo = aioseo()->core->networkCache->get( $cacheKey );
|
|
|
|
if ( null !== $cachedInfo ) {
|
|
if ( false === $cachedInfo ) {
|
|
return $defaultApi;
|
|
}
|
|
|
|
$this->info = $cachedInfo;
|
|
} else {
|
|
$rawResponse = aioseo()->helpers->wpRemotePost( $this->getUrl() . 'info/', [
|
|
'timeout' => 20,
|
|
'body' => wp_json_encode( [
|
|
'license' => $this->key,
|
|
'domain' => aioseo()->helpers->getSiteDomain( true ),
|
|
'sku' => $this->pluginSlug,
|
|
'version' => $this->version,
|
|
'php_version' => PHP_VERSION,
|
|
'wp_version' => get_bloginfo( 'version' )
|
|
] )
|
|
] );
|
|
|
|
if ( is_wp_error( $rawResponse ) ) {
|
|
return $defaultApi;
|
|
}
|
|
|
|
$response = json_decode( wp_remote_retrieve_body( $rawResponse ) );
|
|
if ( empty( $response ) || property_exists( $response, 'error' ) ) {
|
|
$this->info = false;
|
|
|
|
aioseo()->core->networkCache->update( $cacheKey, false, 10 * MINUTE_IN_SECONDS );
|
|
|
|
return $defaultApi;
|
|
}
|
|
|
|
$this->info = $response;
|
|
|
|
aioseo()->core->networkCache->update( $cacheKey, $response, HOUR_IN_SECONDS );
|
|
}
|
|
}
|
|
|
|
// Create a new stdClass object and populate it with our plugin information.
|
|
$api = new \stdClass();
|
|
$api->name = $this->info->name ?? '';
|
|
$api->slug = $this->info->slug ?? '';
|
|
$api->version = $this->info->version ?? '';
|
|
$api->author = $this->info->author ?? '';
|
|
$api->author_profile = $this->info->author_profile ?? '';
|
|
$api->requires = $this->info->requires ?? '';
|
|
$api->tested = $this->info->tested ?? '';
|
|
$api->last_updated = $this->info->last_updated ?? '';
|
|
$api->homepage = $this->info->homepage ?? '';
|
|
$api->sections['description'] = $this->info->description ?? '';
|
|
$api->sections['changelog'] = $this->info->changelog ?? '';
|
|
$api->download_link = $this->info->download_link ?? '';
|
|
$api->active_installs = $this->info->active_installs ?? '';
|
|
$api->banners = isset( $this->info->banners ) ? (array) $this->info->banners : '';
|
|
|
|
// Return the new API object with our custom data.
|
|
return $api;
|
|
}
|
|
|
|
/**
|
|
* Get the URL to check licenses.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return string The URL.
|
|
*/
|
|
public function getUrl() {
|
|
if ( defined( 'AIOSEO_LICENSING_URL' ) ) {
|
|
return AIOSEO_LICENSING_URL;
|
|
}
|
|
|
|
return $this->baseUrl;
|
|
}
|
|
} |