Files
WooNooW/templates/updater/class-woonoow-updater.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);
}
}
}