'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); } } }