true) // Optional: Add license settings page * ); * * @version 1.1.0 * @author WP Digital Download */ if (!defined('ABSPATH')) { exit; } if (!class_exists('WPDD_Plugin_Updater')) { class WPDD_Plugin_Updater { private $plugin_slug; private $plugin_file; private $version; private $license_key; private $update_server; private $transient_key; /** * Initialize the updater * * @param string $plugin_file Full path to the main plugin file * @param string $license_key Your license key * @param string $update_server URL to your update server * @param array $args Additional arguments */ public function __construct($plugin_file, $license_key, $update_server, $args = array()) { // Validate required parameters if (empty($plugin_file) || !file_exists($plugin_file)) { error_log('WPDD Updater: Invalid plugin file path provided'); return; } if (empty($update_server) || !filter_var($update_server, FILTER_VALIDATE_URL)) { error_log('WPDD Updater: Invalid update server URL provided'); return; } $this->plugin_file = $plugin_file; $this->plugin_slug = basename($plugin_file, '.php'); $this->license_key = trim($license_key); $this->update_server = trailingslashit($update_server); // Get plugin version from header if (!function_exists('get_plugin_data')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_data = get_plugin_data($plugin_file); $this->version = $plugin_data['Version'] ?? '1.0.0'; $this->transient_key = 'wpdd_update_' . $this->plugin_slug; // Initialize hooks $this->init_hooks(); // Add settings page if requested if (isset($args['add_settings_page']) && $args['add_settings_page']) { $this->add_settings_page(); } } /** * Initialize WordPress hooks */ private function init_hooks() { add_filter('pre_set_site_transient_update_plugins', array($this, 'check_for_update')); add_filter('plugins_api', array($this, 'plugin_info'), 10, 3); add_filter('upgrader_pre_download', array($this, 'maybe_download_package'), 10, 3); // Clean up transients on plugin activation/deactivation register_activation_hook($this->plugin_file, array($this, 'delete_transients')); register_deactivation_hook($this->plugin_file, array($this, 'delete_transients')); } /** * Check for plugin updates */ public function check_for_update($transient) { if (empty($transient->checked)) { return $transient; } // Get cached update info $update_cache = get_transient($this->transient_key); if ($update_cache !== false) { if (isset($update_cache->update_available) && $update_cache->update_available) { $transient->response[$this->plugin_file] = $update_cache; } return $transient; } // Check for update from server $update_info = $this->request_update_info(); if ($update_info && isset($update_info['update_available']) && $update_info['update_available']) { // API returns proper structure - use it directly $plugin_data = array( 'slug' => $update_info['slug'] ?? $this->plugin_slug, 'plugin' => $update_info['plugin'] ?? $this->plugin_file, 'new_version' => $update_info['version'] ?? $update_info['new_version'], 'url' => $update_info['url'] ?? '', 'package' => $update_info['package'] ?? $update_info['download_url'], 'tested' => $update_info['tested'] ?? get_bloginfo('version'), 'requires' => $update_info['requires'] ?? '5.0', 'requires_php' => $update_info['requires_php'] ?? '7.0', 'compatibility' => new stdClass() ); $update_cache = (object) $plugin_data; $update_cache->update_available = true; // Cache for 12 hours set_transient($this->transient_key, $update_cache, 12 * HOUR_IN_SECONDS); $transient->response[$this->plugin_file] = $update_cache; } else { // No update available - cache negative result for 12 hours $update_cache = new stdClass(); $update_cache->update_available = false; set_transient($this->transient_key, $update_cache, 12 * HOUR_IN_SECONDS); } return $transient; } /** * Provide plugin information for the update screen */ public function plugin_info($false, $action, $args) { if ($action !== 'plugin_information' || $args->slug !== $this->plugin_slug) { return $false; } $update_info = $this->request_update_info(); if (!$update_info) { return $false; } // Extract product name from plugin file if available if (!function_exists('get_plugin_data')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_data = get_plugin_data($this->plugin_file); return (object) array( 'slug' => $this->plugin_slug, 'name' => $plugin_data['Name'] ?? $this->plugin_slug, 'version' => $update_info['version'] ?? $update_info['new_version'] ?? $this->version, 'author' => $plugin_data['Author'] ?? $update_info['author'] ?? '', 'homepage' => $plugin_data['PluginURI'] ?? $update_info['url'] ?? '', 'requires' => $update_info['requires'] ?? '5.0', 'tested' => $update_info['tested'] ?? get_bloginfo('version'), 'requires_php' => $update_info['requires_php'] ?? '7.0', 'download_link' => $update_info['package'] ?? $update_info['download_url'] ?? '', 'sections' => array( 'changelog' => $update_info['changelog'] ?? $update_info['release_notes'] ?? '', 'description' => $plugin_data['Description'] ?? $update_info['description'] ?? '' ), 'banners' => array(), 'icons' => array() ); } /** * Handle package download with license validation */ public function maybe_download_package($reply, $package, $upgrader) { // Check if this is our plugin's package // More flexible URL checking - handle different URL structures $server_host = parse_url($this->update_server, PHP_URL_HOST); $package_host = parse_url($package, PHP_URL_HOST); if ($server_host !== $package_host && strpos($package, $this->plugin_slug) === false) { return $reply; } // Also check if this is a WPDD download URL pattern if (strpos($package, 'wp-json/wpdd/v1/download-update') === false && strpos($package, $this->update_server) === false) { return $reply; } // Validate license before download $license_valid = $this->validate_license(); if (!$license_valid) { return new WP_Error('license_invalid', __('Your license key is invalid or expired. Please update your license key.')); } return $reply; } /** * Request update information from server */ private function request_update_info() { $url = $this->update_server . "wp-json/wpdd/v1/check-update/{$this->plugin_slug}"; $url = add_query_arg(array( 'license_key' => $this->license_key, 'version' => $this->version, 'site_url' => home_url() ), $url); $response = wp_remote_get($url, array( 'timeout' => 30, 'redirection' => 3, 'httpversion' => '1.1', 'user-agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(), 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json' ), 'sslverify' => true )); if (is_wp_error($response)) { error_log('WPDD Updater: Failed to check for updates - ' . $response->get_error_message()); return false; } $http_code = wp_remote_retrieve_response_code($response); if ($http_code !== 200) { error_log('WPDD Updater: HTTP error ' . $http_code . ' when checking for updates'); return false; } $body = wp_remote_retrieve_body($response); if (empty($body)) { error_log('WPDD Updater: Empty response body when checking for updates'); return false; } $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log('WPDD Updater: Invalid JSON response: ' . json_last_error_msg()); return false; } if (!$data || !isset($data['success'])) { error_log('WPDD Updater: Invalid response from update server'); return false; } if (!$data['success']) { if (isset($data['error'])) { error_log('WPDD Updater: ' . $data['error'] . ' - ' . ($data['message'] ?? '')); } return false; } // Return the data array directly - API already provides proper structure return $data; } /** * Validate license with server */ public function validate_license() { if (empty($this->license_key)) { return false; } $url = $this->update_server . 'wp-json/wpdd/v1/validate-license'; $response = wp_remote_post($url, array( 'timeout' => 30, 'redirection' => 3, 'httpversion' => '1.1', 'body' => array( 'license_key' => $this->license_key, 'product_slug' => $this->plugin_slug, 'site_url' => home_url() ), 'headers' => array( 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(), 'Accept' => 'application/json' ), 'sslverify' => true )); if (is_wp_error($response)) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); return $data && isset($data['success']) && $data['success']; } /** * Activate license */ public function activate_license($license_key = null) { if ($license_key) { $this->license_key = $license_key; } if (empty($this->license_key)) { return array( 'success' => false, 'message' => __('Please enter a license key.') ); } $url = $this->update_server . 'wp-json/wpdd/v1/activate-license'; $response = wp_remote_post($url, array( 'timeout' => 30, 'redirection' => 3, 'httpversion' => '1.1', 'body' => array( 'license_key' => $this->license_key, 'site_url' => home_url(), 'site_name' => get_bloginfo('name'), 'wp_version' => get_bloginfo('version'), 'php_version' => PHP_VERSION ), 'headers' => array( 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(), 'Accept' => 'application/json' ), 'sslverify' => true )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!$data) { return array( 'success' => false, 'message' => __('Invalid response from server.') ); } return $data; } /** * Deactivate license */ public function deactivate_license() { if (empty($this->license_key)) { return array( 'success' => false, 'message' => __('No license key to deactivate.') ); } $url = $this->update_server . 'wp-json/wpdd/v1/deactivate-license'; $response = wp_remote_post($url, array( 'timeout' => 30, 'redirection' => 3, 'httpversion' => '1.1', 'body' => array( 'license_key' => $this->license_key, 'site_url' => home_url() ), 'headers' => array( 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(), 'Accept' => 'application/json' ), 'sslverify' => true )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); return $data ?: array( 'success' => false, 'message' => __('Invalid response from server.') ); } /** * Add a simple settings page for license management */ private function add_settings_page() { add_action('admin_menu', array($this, 'add_license_menu')); add_action('admin_init', array($this, 'handle_license_actions')); } /** * Add license menu page */ public function add_license_menu() { add_options_page( sprintf(__('%s License', 'default'), $this->plugin_slug), sprintf(__('%s License', 'default'), $this->plugin_slug), 'manage_options', $this->plugin_slug . '-license', array($this, 'render_license_page') ); } /** * Handle license activation/deactivation */ public function handle_license_actions() { if (!current_user_can('manage_options')) { return; } $option_key = $this->plugin_slug . '_license_key'; if (isset($_POST['activate_license'])) { if (!wp_verify_nonce($_POST['license_nonce'], 'wpdd_license_nonce')) { return; } $license_key = sanitize_text_field($_POST['license_key']); $result = $this->activate_license($license_key); if ($result['success']) { update_option($option_key, $license_key); $this->license_key = $license_key; add_settings_error('wpdd_license', 'activated', $result['message'], 'updated'); } else { add_settings_error('wpdd_license', 'activation_failed', $result['message'], 'error'); } } if (isset($_POST['deactivate_license'])) { if (!wp_verify_nonce($_POST['license_nonce'], 'wpdd_license_nonce')) { return; } $result = $this->deactivate_license(); if ($result['success']) { delete_option($option_key); $this->license_key = ''; add_settings_error('wpdd_license', 'deactivated', $result['message'], 'updated'); } else { add_settings_error('wpdd_license', 'deactivation_failed', $result['message'], 'error'); } } } /** * Render license management page */ public function render_license_page() { $option_key = $this->plugin_slug . '_license_key'; $license_key = get_option($option_key, ''); $license_status = $this->validate_license(); ?>

plugin_slug)); ?>

transient_key); delete_site_transient('update_plugins'); } } } // End class exists check