392 lines
13 KiB
PHP
392 lines
13 KiB
PHP
<?php
|
|
|
|
/**
|
|
* WooNooW Software Updater
|
|
*
|
|
* Embed this class in your WordPress plugin or theme to enable
|
|
* automatic updates from your WooNooW-powered store.
|
|
*
|
|
* @version 1.0.0
|
|
* @package WooNooW_Updater
|
|
* @link https://woonoow.com/docs/developer/software-updates
|
|
*
|
|
* USAGE:
|
|
*
|
|
* 1. Copy this file to your plugin/theme (e.g., includes/class-woonoow-updater.php)
|
|
*
|
|
* 2. Include and initialize in your main plugin file:
|
|
*
|
|
* require_once plugin_dir_path(__FILE__) . 'includes/class-woonoow-updater.php';
|
|
*
|
|
* new WooNooW_Updater([
|
|
* 'api_url' => 'https://your-store.com/',
|
|
* 'slug' => 'my-plugin',
|
|
* 'version' => MY_PLUGIN_VERSION,
|
|
* 'license_key' => get_option('my_plugin_license_key'),
|
|
* 'plugin_file' => __FILE__, // For plugins
|
|
* // OR
|
|
* 'theme_slug' => 'my-theme', // For themes
|
|
* ]);
|
|
*/
|
|
|
|
if (!class_exists('WooNooW_Updater')) {
|
|
|
|
class WooNooW_Updater
|
|
{
|
|
/**
|
|
* @var string API base URL
|
|
*/
|
|
private $api_url;
|
|
|
|
/**
|
|
* @var string Software slug
|
|
*/
|
|
private $slug;
|
|
|
|
/**
|
|
* @var string Plugin file path (for plugins)
|
|
*/
|
|
private $plugin_file;
|
|
|
|
/**
|
|
* @var string Theme slug (for themes)
|
|
*/
|
|
private $theme_slug;
|
|
|
|
/**
|
|
* @var string Current version
|
|
*/
|
|
private $version;
|
|
|
|
/**
|
|
* @var string License key
|
|
*/
|
|
private $license_key;
|
|
|
|
/**
|
|
* @var string Cache key for transient
|
|
*/
|
|
private $cache_key;
|
|
|
|
/**
|
|
* @var int Cache TTL in seconds (default: 12 hours)
|
|
*/
|
|
private $cache_ttl = 43200;
|
|
|
|
/**
|
|
* @var bool Is this a theme update?
|
|
*/
|
|
private $is_theme = false;
|
|
|
|
/**
|
|
* Initialize the updater
|
|
*
|
|
* @param array $config Configuration array
|
|
*/
|
|
public function __construct($config)
|
|
{
|
|
$this->api_url = trailingslashit($config['api_url'] ?? '');
|
|
$this->slug = $config['slug'] ?? '';
|
|
$this->version = $config['version'] ?? '1.0.0';
|
|
$this->license_key = $config['license_key'] ?? '';
|
|
$this->cache_key = 'woonoow_update_' . md5($this->slug);
|
|
|
|
// Determine if plugin or theme
|
|
if (!empty($config['theme_slug'])) {
|
|
$this->is_theme = true;
|
|
$this->theme_slug = $config['theme_slug'];
|
|
} else {
|
|
$this->plugin_file = $config['plugin_file'] ?? '';
|
|
}
|
|
|
|
// Set cache TTL if provided
|
|
if (!empty($config['cache_ttl'])) {
|
|
$this->cache_ttl = (int) $config['cache_ttl'] * 3600; // Convert hours to seconds
|
|
}
|
|
|
|
// Don't proceed if no API URL or license
|
|
if (empty($this->api_url) || empty($this->slug)) {
|
|
return;
|
|
}
|
|
|
|
// Hook into WordPress update system
|
|
if ($this->is_theme) {
|
|
add_filter('pre_set_site_transient_update_themes', [$this, 'check_theme_update']);
|
|
add_filter('themes_api', [$this, 'theme_info'], 20, 3);
|
|
} else {
|
|
add_filter('pre_set_site_transient_update_plugins', [$this, 'check_plugin_update']);
|
|
add_filter('plugins_api', [$this, 'plugin_info'], 20, 3);
|
|
}
|
|
|
|
// Clear cache on upgrade
|
|
add_action('upgrader_process_complete', [$this, 'clear_cache'], 10, 2);
|
|
}
|
|
|
|
/**
|
|
* Check for plugin updates
|
|
*
|
|
* @param object $transient Update transient
|
|
* @return object Modified transient
|
|
*/
|
|
public function check_plugin_update($transient)
|
|
{
|
|
if (empty($transient->checked)) {
|
|
return $transient;
|
|
}
|
|
|
|
$remote = $this->get_remote_info();
|
|
|
|
if ($remote && !empty($remote->latest_version)) {
|
|
if (version_compare($this->version, $remote->latest_version, '<')) {
|
|
$plugin_file = plugin_basename($this->plugin_file);
|
|
|
|
$transient->response[$plugin_file] = (object) [
|
|
'slug' => $this->slug,
|
|
'plugin' => $plugin_file,
|
|
'new_version' => $remote->latest_version,
|
|
'package' => $remote->download_url ?? '',
|
|
'url' => $remote->homepage ?? '',
|
|
'tested' => $remote->wordpress->tested ?? '',
|
|
'requires' => $remote->wordpress->requires ?? '',
|
|
'requires_php' => $remote->wordpress->requires_php ?? '',
|
|
'icons' => (array) ($remote->wordpress->icons ?? []),
|
|
'banners' => (array) ($remote->wordpress->banners ?? []),
|
|
];
|
|
} else {
|
|
// No update available
|
|
$transient->no_update[plugin_basename($this->plugin_file)] = (object) [
|
|
'slug' => $this->slug,
|
|
'plugin' => plugin_basename($this->plugin_file),
|
|
'new_version' => $this->version,
|
|
];
|
|
}
|
|
}
|
|
|
|
return $transient;
|
|
}
|
|
|
|
/**
|
|
* Check for theme updates
|
|
*
|
|
* @param object $transient Update transient
|
|
* @return object Modified transient
|
|
*/
|
|
public function check_theme_update($transient)
|
|
{
|
|
if (empty($transient->checked)) {
|
|
return $transient;
|
|
}
|
|
|
|
$remote = $this->get_remote_info();
|
|
|
|
if ($remote && !empty($remote->latest_version)) {
|
|
if (version_compare($this->version, $remote->latest_version, '<')) {
|
|
$transient->response[$this->theme_slug] = [
|
|
'theme' => $this->theme_slug,
|
|
'new_version' => $remote->latest_version,
|
|
'package' => $remote->download_url ?? '',
|
|
'url' => $remote->homepage ?? '',
|
|
'requires' => $remote->wordpress->requires ?? '',
|
|
'requires_php' => $remote->wordpress->requires_php ?? '',
|
|
];
|
|
}
|
|
}
|
|
|
|
return $transient;
|
|
}
|
|
|
|
/**
|
|
* Provide plugin information for details popup
|
|
*
|
|
* @param mixed $result Default result
|
|
* @param string $action Action being performed
|
|
* @param object $args Arguments
|
|
* @return mixed Plugin info or default result
|
|
*/
|
|
public function plugin_info($result, $action, $args)
|
|
{
|
|
if ($action !== 'plugin_information' || !isset($args->slug) || $args->slug !== $this->slug) {
|
|
return $result;
|
|
}
|
|
|
|
$remote = $this->get_remote_info();
|
|
|
|
if (!$remote) {
|
|
return $result;
|
|
}
|
|
|
|
return (object) [
|
|
'name' => $remote->product->name ?? $this->slug,
|
|
'slug' => $this->slug,
|
|
'version' => $remote->latest_version,
|
|
'tested' => $remote->wordpress->tested ?? '',
|
|
'requires' => $remote->wordpress->requires ?? '',
|
|
'requires_php' => $remote->wordpress->requires_php ?? '',
|
|
'author' => $remote->author ?? '',
|
|
'homepage' => $remote->homepage ?? '',
|
|
'download_link' => $remote->download_url ?? '',
|
|
'sections' => [
|
|
'description' => $remote->description ?? '',
|
|
'changelog' => nl2br($remote->changelog ?? ''),
|
|
],
|
|
'banners' => (array) ($remote->wordpress->banners ?? []),
|
|
'icons' => (array) ($remote->wordpress->icons ?? []),
|
|
'last_updated' => $remote->release_date ?? '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Provide theme information for details popup
|
|
*
|
|
* @param mixed $result Default result
|
|
* @param string $action Action being performed
|
|
* @param object $args Arguments
|
|
* @return mixed Theme info or default result
|
|
*/
|
|
public function theme_info($result, $action, $args)
|
|
{
|
|
if ($action !== 'theme_information' || !isset($args->slug) || $args->slug !== $this->theme_slug) {
|
|
return $result;
|
|
}
|
|
|
|
$remote = $this->get_remote_info();
|
|
|
|
if (!$remote) {
|
|
return $result;
|
|
}
|
|
|
|
return (object) [
|
|
'name' => $remote->product->name ?? $this->theme_slug,
|
|
'slug' => $this->theme_slug,
|
|
'version' => $remote->latest_version,
|
|
'requires' => $remote->wordpress->requires ?? '',
|
|
'requires_php' => $remote->wordpress->requires_php ?? '',
|
|
'author' => $remote->author ?? '',
|
|
'homepage' => $remote->homepage ?? '',
|
|
'download_link' => $remote->download_url ?? '',
|
|
'sections' => [
|
|
'description' => $remote->description ?? '',
|
|
'changelog' => nl2br($remote->changelog ?? ''),
|
|
],
|
|
'last_updated' => $remote->release_date ?? '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get remote version info from API
|
|
*
|
|
* @return object|null Remote info or null on failure
|
|
*/
|
|
private function get_remote_info()
|
|
{
|
|
// Check cache first
|
|
$cached = get_transient($this->cache_key);
|
|
if ($cached !== false) {
|
|
if ($cached === 'no_update') {
|
|
return null;
|
|
}
|
|
return $cached;
|
|
}
|
|
|
|
// Make API request
|
|
$response = wp_remote_post($this->api_url . 'wp-json/woonoow/v1/software/check', [
|
|
'timeout' => 15,
|
|
'headers' => [
|
|
'Content-Type' => 'application/json',
|
|
],
|
|
'body' => wp_json_encode([
|
|
'license_key' => $this->license_key,
|
|
'slug' => $this->slug,
|
|
'version' => $this->version,
|
|
'site_url' => home_url(),
|
|
'wp_version' => get_bloginfo('version'),
|
|
'php_version' => phpversion(),
|
|
]),
|
|
]);
|
|
|
|
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
|
|
// Cache failure for shorter time to retry sooner
|
|
set_transient($this->cache_key, 'no_update', 3600);
|
|
return null;
|
|
}
|
|
|
|
$data = json_decode(wp_remote_retrieve_body($response));
|
|
|
|
if (!$data || empty($data->success)) {
|
|
set_transient($this->cache_key, 'no_update', 3600);
|
|
return null;
|
|
}
|
|
|
|
if (empty($data->update_available)) {
|
|
set_transient($this->cache_key, 'no_update', $this->cache_ttl);
|
|
return null;
|
|
}
|
|
|
|
// Cache the update info
|
|
set_transient($this->cache_key, $data, $this->cache_ttl);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Clear update cache after upgrade
|
|
*
|
|
* @param object $upgrader Upgrader instance
|
|
* @param array $options Upgrade options
|
|
*/
|
|
public function clear_cache($upgrader, $options)
|
|
{
|
|
if ($this->is_theme) {
|
|
if ($options['action'] === 'update' && $options['type'] === 'theme') {
|
|
if (isset($options['themes']) && in_array($this->theme_slug, $options['themes'])) {
|
|
delete_transient($this->cache_key);
|
|
}
|
|
}
|
|
} else {
|
|
if ($options['action'] === 'update' && $options['type'] === 'plugin') {
|
|
if (isset($options['plugins']) && in_array(plugin_basename($this->plugin_file), $options['plugins'])) {
|
|
delete_transient($this->cache_key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force check for updates (bypass cache)
|
|
*/
|
|
public function force_check()
|
|
{
|
|
delete_transient($this->cache_key);
|
|
return $this->get_remote_info();
|
|
}
|
|
|
|
/**
|
|
* Get license status
|
|
*
|
|
* @return array|null License status or null on failure
|
|
*/
|
|
public function get_license_status()
|
|
{
|
|
if (empty($this->license_key)) {
|
|
return ['valid' => false, 'error' => 'no_license'];
|
|
}
|
|
|
|
$response = wp_remote_post($this->api_url . 'wp-json/woonoow/v1/licenses/validate', [
|
|
'timeout' => 15,
|
|
'headers' => [
|
|
'Content-Type' => 'application/json',
|
|
],
|
|
'body' => wp_json_encode([
|
|
'license_key' => $this->license_key,
|
|
]),
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return null;
|
|
}
|
|
|
|
return json_decode(wp_remote_retrieve_body($response), true);
|
|
}
|
|
}
|
|
}
|