Version 1.4.1 - Localhost license bypass and auto-updates
All checks were successful
Create Release Package / build-release (push) Successful in 1m3s

Added localhost/self-licensing license bypass and WordPress auto-update
integration from license server.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 11:55:41 +01:00
parent a4b84f7e41
commit fa26247d1b
21 changed files with 1940 additions and 38 deletions

View File

@@ -0,0 +1,333 @@
<?php
/**
* License Checker
*
* Handles license validation with localhost and self-licensing bypass
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* WC_TPP_License_Checker class
*/
if (!class_exists('WC_TPP_License_Checker')) {
class WC_TPP_License_Checker {
/**
* Singleton instance
*
* @var WC_TPP_License_Checker|null
*/
private static $instance = null;
/**
* Cache key for license status
*/
private const CACHE_KEY = 'wc_tpp_license_status';
/**
* Cache TTL for successful validation (1 hour)
*/
private const CACHE_TTL_SUCCESS = 3600;
/**
* Cache TTL for failed validation (5 minutes)
*/
private const CACHE_TTL_ERROR = 300;
/**
* Localhost patterns
*
* @var array
*/
private const LOCALHOST_HOSTS = ['localhost', '127.0.0.1', '::1'];
/**
* Localhost TLDs
*
* @var array
*/
private const LOCALHOST_TLDS = ['.localhost', '.local', '.test', '.example', '.invalid'];
/**
* Get singleton instance
*
* @return WC_TPP_License_Checker
*/
public static function get_instance(): WC_TPP_License_Checker {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
// Clear cache when license settings change
add_action('update_option_wc_tpp_license_key', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_url', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_secret', array($this, 'clear_cache'));
}
/**
* Check if the current environment is localhost
*
* Matches patterns:
* - localhost
* - 127.0.0.1
* - ::1 (IPv6 localhost)
* - *.localhost (subdomains)
* - *.local (subdomains)
* - *.test (RFC 2606)
* - *.example (RFC 2606)
* - *.invalid (RFC 2606)
*
* @return bool
*/
public function is_localhost(): bool {
$domain = $this->get_current_domain();
// Remove port number if present
$domain = preg_replace('/:[\d]+$/', '', $domain);
$domain = strtolower($domain);
// Check exact matches
if (in_array($domain, self::LOCALHOST_HOSTS, true)) {
return true;
}
// Check TLD patterns
foreach (self::LOCALHOST_TLDS as $tld) {
if (str_ends_with($domain, $tld)) {
return true;
}
}
// Check for private IP ranges (Docker, VMs, etc.)
if ($this->is_private_ip($domain)) {
return true;
}
return false;
}
/**
* Check if domain is a private IP address
*
* @param string $domain The domain to check.
* @return bool
*/
private function is_private_ip(string $domain): bool {
// Check if it's a valid IP first
if (!filter_var($domain, FILTER_VALIDATE_IP)) {
return false;
}
// Check for private/reserved ranges
return !filter_var(
$domain,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
);
}
/**
* Check if the current site is self-licensing
*
* Returns true if the license server URL and site URL are on the same domain.
* This allows the license server itself to use the plugin without a license.
*
* @return bool
*/
public function is_self_licensing(): bool {
$server_url = get_option('wc_tpp_license_server_url', '');
if (empty($server_url)) {
return false;
}
$server_domain = $this->normalize_domain($server_url);
$site_domain = $this->normalize_domain(get_site_url());
return $server_domain === $site_domain;
}
/**
* Normalize a URL to its domain
*
* @param string $url The URL to normalize.
* @return string
*/
private function normalize_domain(string $url): string {
$parsed = wp_parse_url($url);
$host = $parsed['host'] ?? '';
// Convert to lowercase
$host = strtolower($host);
// Remove www. prefix
$host = preg_replace('/^www\./', '', $host);
return $host;
}
/**
* Get current domain from site URL
*
* @return string
*/
public function get_current_domain(): string {
$site_url = get_site_url();
$parsed = wp_parse_url($site_url);
$host = $parsed['host'] ?? 'localhost';
// Include port if non-standard
if (isset($parsed['port'])) {
$host .= ':' . $parsed['port'];
}
return strtolower($host);
}
/**
* Check if the license is valid
*
* This is the main entry point for license validation.
* It implements the bypass logic for localhost and self-licensing.
*
* @return bool
*/
public function is_license_valid(): bool {
// Always valid on localhost
if ($this->is_localhost()) {
return true;
}
// Always valid for self-licensing
if ($this->is_self_licensing()) {
return true;
}
// Check cached status
$cached = $this->get_cached_status();
if (false !== $cached) {
return !empty($cached['valid']);
}
// Validate against server
return $this->validate_license();
}
/**
* Get the license bypass reason if applicable
*
* @return string|null 'localhost', 'self_licensing', or null
*/
public function get_bypass_reason(): ?string {
if ($this->is_localhost()) {
return 'localhost';
}
if ($this->is_self_licensing()) {
return 'self_licensing';
}
return null;
}
/**
* Get cached license status
*
* @return array|false
*/
public function get_cached_status() {
return get_transient(self::CACHE_KEY);
}
/**
* Validate license against the server
*
* @return bool
*/
private function validate_license(): bool {
$license_key = get_option('wc_tpp_license_key', '');
$server_url = get_option('wc_tpp_license_server_url', '');
$server_secret = get_option('wc_tpp_license_server_secret', '');
// Can't validate without credentials
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
return false;
}
try {
$client = $this->get_license_client($server_url, $server_secret);
$domain = $this->get_current_domain();
// Remove port for validation
$domain = preg_replace('/:[\d]+$/', '', $domain);
$result = $client->validate($license_key, $domain);
// Cache successful validation
set_transient(self::CACHE_KEY, array(
'valid' => true,
'product_id' => $result->productId,
'expires_at' => $result->expiresAt?->format('Y-m-d H:i:s'),
'is_lifetime' => $result->isLifetime(),
'checked_at' => current_time('mysql'),
), self::CACHE_TTL_SUCCESS);
return true;
} catch (\Exception $e) {
// Cache validation failure
set_transient(self::CACHE_KEY, array(
'valid' => false,
'error' => $e->getMessage(),
'checked_at' => current_time('mysql'),
), self::CACHE_TTL_ERROR);
return false;
}
}
/**
* Get license client instance
*
* @param string $server_url License server URL.
* @param string $server_secret Shared secret for signature verification.
* @return \Magdev\WcLicensedProductClient\LicenseClientInterface
*/
private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface {
$httpClient = \Symfony\Component\HttpClient\HttpClient::create();
return new \Magdev\WcLicensedProductClient\SecureLicenseClient(
httpClient: $httpClient,
baseUrl: $server_url,
serverSecret: $server_secret,
);
}
/**
* Clear the license cache
*/
public function clear_cache(): void {
delete_transient(self::CACHE_KEY);
}
/**
* Force revalidation of the license
*
* @return bool
*/
public function revalidate(): bool {
$this->clear_cache();
return $this->is_license_valid();
}
}
}

View File

@@ -33,6 +33,9 @@ if (!class_exists('WC_TPP_Settings')) {
// Add AJAX handlers for license validation
add_action('wp_ajax_wc_tpp_validate_license', array($this, 'ajax_validate_license'));
add_action('wp_ajax_wc_tpp_activate_license', array($this, 'ajax_activate_license'));
// Add AJAX handler for update checks
add_action('wp_ajax_wc_tpp_check_updates', array($this, 'ajax_check_updates'));
}
/**
@@ -44,6 +47,7 @@ if (!class_exists('WC_TPP_Settings')) {
return array(
'' => __('General', 'wc-tier-package-prices'),
'license' => __('License', 'wc-tier-package-prices'),
'updates' => __('Auto-Updates', 'wc-tier-package-prices'),
);
}
@@ -177,6 +181,75 @@ if (!class_exists('WC_TPP_Settings')) {
);
}
/**
* Get settings for the Auto-Updates section
*
* @return array
*/
protected function get_settings_for_updates_section() {
// Check if auto-install is available (requires valid license or bypass)
$license_checker = WC_TPP_License_Checker::get_instance();
$auto_install_disabled = !$license_checker->is_license_valid();
$auto_install_desc = __('Automatically install updates when available.', 'wc-tier-package-prices');
if ($auto_install_disabled) {
$auto_install_desc .= ' ' . __('(Requires a valid license)', 'wc-tier-package-prices');
}
return array(
array(
'title' => __('Auto-Update Settings', 'wc-tier-package-prices'),
'type' => 'title',
'desc' => __('Configure automatic plugin updates from the license server.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_settings',
),
array(
'title' => __('Enable Update Notifications', 'wc-tier-package-prices'),
'desc' => __('Check for available plugin updates.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_notification_enabled',
'default' => 'yes',
'type' => 'checkbox',
'desc_tip' => __('When enabled, the plugin will check for updates from the license server and show notifications in the WordPress admin.', 'wc-tier-package-prices'),
),
array(
'title' => __('Automatically Install Updates', 'wc-tier-package-prices'),
'desc' => $auto_install_desc,
'id' => 'wc_tpp_auto_install_enabled',
'default' => 'no',
'type' => 'checkbox',
'desc_tip' => __('When enabled, updates will be automatically installed when WordPress performs background updates.', 'wc-tier-package-prices'),
'custom_attributes' => $auto_install_disabled ? array('disabled' => 'disabled') : array(),
),
array(
'title' => __('Check Frequency (Hours)', 'wc-tier-package-prices'),
'desc' => __('How often to check for updates.', 'wc-tier-package-prices'),
'id' => 'wc_tpp_update_check_frequency',
'default' => '12',
'type' => 'number',
'css' => 'width: 80px;',
'desc_tip' => __('Number of hours between update checks. Default is 12 hours.', 'wc-tier-package-prices'),
'custom_attributes' => array(
'min' => '1',
'max' => '168',
),
),
array(
'title' => __('Update Status', 'wc-tier-package-prices'),
'type' => 'wc_tpp_update_status',
'id' => 'wc_tpp_update_status_display',
),
array(
'type' => 'sectionend',
'id' => 'wc_tpp_update_settings',
),
);
}
/**
* Get cached license status
*
@@ -368,8 +441,9 @@ if (!class_exists('WC_TPP_Settings')) {
public function output() {
global $current_section;
// Register custom field type for license status display
// Register custom field types
add_action('woocommerce_admin_field_wc_tpp_license_status', array($this, 'output_license_status_field'));
add_action('woocommerce_admin_field_wc_tpp_update_status', array($this, 'output_update_status_field'));
parent::output();
@@ -377,6 +451,11 @@ if (!class_exists('WC_TPP_Settings')) {
if ('license' === $current_section) {
$this->output_license_scripts();
}
// Add JavaScript for updates section
if ('updates' === $current_section) {
$this->output_updates_scripts();
}
}
/**
@@ -415,6 +494,20 @@ if (!class_exists('WC_TPP_Settings')) {
* @param array|false $status License status data.
*/
private function render_license_status_html($status) {
// Check for license bypass
$license_checker = WC_TPP_License_Checker::get_instance();
$bypass_reason = $license_checker->get_bypass_reason();
if ($bypass_reason) {
echo '<span class="wc-tpp-license-active">' . esc_html__('License Active', 'wc-tier-package-prices') . '</span>';
if ('localhost' === $bypass_reason) {
echo '<br><small>' . esc_html__('(Localhost environment - license validation bypassed)', 'wc-tier-package-prices') . '</small>';
} elseif ('self_licensing' === $bypass_reason) {
echo '<br><small>' . esc_html__('(Self-licensing server - license validation bypassed)', 'wc-tier-package-prices') . '</small>';
}
return;
}
if (empty($status)) {
echo '<span class="wc-tpp-license-inactive">' . esc_html__('No license activated', 'wc-tier-package-prices') . '</span>';
return;
@@ -443,6 +536,50 @@ if (!class_exists('WC_TPP_Settings')) {
}
}
/**
* Output update status custom field
*
* @param array $value Field configuration.
*/
public function output_update_status_field($value) {
$update_checker = WC_TPP_Update_Checker::get_instance();
$available_version = $update_checker->get_available_version();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label><?php esc_html_e('Update Status', 'wc-tier-package-prices'); ?></label>
</th>
<td class="forminp">
<div id="wc-tpp-update-status-container">
<?php $this->render_update_status_html($available_version); ?>
</div>
<p class="description" style="margin-top: 10px;">
<button type="button" class="button" id="wc-tpp-check-updates">
<?php esc_html_e('Check for Updates', 'wc-tier-package-prices'); ?>
</button>
<span class="spinner" id="wc-tpp-update-spinner"></span>
</p>
</td>
</tr>
<?php
}
/**
* Render update status HTML
*
* @param string|null $available_version Available update version.
*/
private function render_update_status_html(?string $available_version) {
echo '<p><strong>' . esc_html__('Current Version:', 'wc-tier-package-prices') . '</strong> ' . esc_html(WC_TPP_VERSION) . '</p>';
if ($available_version) {
echo '<p class="wc-tpp-update-available"><strong>' . esc_html__('Update Available:', 'wc-tier-package-prices') . '</strong> ' . esc_html($available_version) . '</p>';
echo '<p><a href="' . esc_url(admin_url('update-core.php')) . '" class="button button-primary">' . esc_html__('Update Now', 'wc-tier-package-prices') . '</a></p>';
} else {
echo '<p class="wc-tpp-up-to-date">' . esc_html__('You are running the latest version.', 'wc-tier-package-prices') . '</p>';
}
}
/**
* Output JavaScript for license management
*/
@@ -525,5 +662,87 @@ if (!class_exists('WC_TPP_Settings')) {
</script>
<?php
}
/**
* AJAX handler for checking updates
*/
public function ajax_check_updates() {
check_ajax_referer('wc_tpp_update_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
}
$update_checker = WC_TPP_Update_Checker::get_instance();
$update_info = $update_checker->force_check();
if ($update_info && !empty($update_info['update_available'])) {
wp_send_json_success(array(
'message' => sprintf(
/* translators: %s: Version number */
__('Update available: version %s', 'wc-tier-package-prices'),
$update_info['version']
),
'update_available' => true,
'version' => $update_info['version'],
'current_version' => WC_TPP_VERSION,
));
} else {
wp_send_json_success(array(
'message' => __('You are running the latest version.', 'wc-tier-package-prices'),
'update_available' => false,
'current_version' => WC_TPP_VERSION,
));
}
}
/**
* Output JavaScript for update management
*/
private function output_updates_scripts() {
$nonce = wp_create_nonce('wc_tpp_update_nonce');
?>
<script type="text/javascript">
jQuery(function($) {
var $checkBtn = $('#wc-tpp-check-updates');
var $spinner = $('#wc-tpp-update-spinner');
var $container = $('#wc-tpp-update-status-container');
$checkBtn.on('click', function() {
$spinner.addClass('is-active');
$checkBtn.prop('disabled', true);
$.post(ajaxurl, {
action: 'wc_tpp_check_updates',
nonce: '<?php echo esc_js($nonce); ?>'
})
.done(function(response) {
if (response.success) {
var html = '<p><strong><?php echo esc_js(__('Current Version:', 'wc-tier-package-prices')); ?></strong> ' + response.data.current_version + '</p>';
if (response.data.update_available) {
html += '<p class="wc-tpp-update-available"><strong><?php echo esc_js(__('Update Available:', 'wc-tier-package-prices')); ?></strong> ' + response.data.version + '</p>';
html += '<p><a href="<?php echo esc_url(admin_url('update-core.php')); ?>" class="button button-primary"><?php echo esc_js(__('Update Now', 'wc-tier-package-prices')); ?></a></p>';
} else {
html += '<p class="wc-tpp-up-to-date"><?php echo esc_js(__('You are running the latest version.', 'wc-tier-package-prices')); ?></p>';
}
$container.html(html);
} else {
alert(response.data.message || '<?php echo esc_js(__('Failed to check for updates.', 'wc-tier-package-prices')); ?>');
}
})
.fail(function() {
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
})
.always(function() {
$spinner.removeClass('is-active');
$checkBtn.prop('disabled', false);
});
});
});
</script>
<?php
}
}
}

View File

@@ -0,0 +1,468 @@
<?php
/**
* Update Checker
*
* Handles WordPress plugin updates from the license server
*
* @package WC_Tier_Package_Prices
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* WC_TPP_Update_Checker class
*/
if (!class_exists('WC_TPP_Update_Checker')) {
class WC_TPP_Update_Checker {
/**
* Singleton instance
*
* @var WC_TPP_Update_Checker|null
*/
private static $instance = null;
/**
* Plugin slug
*/
private const PLUGIN_SLUG = 'wc-tier-and-package-prices';
/**
* Update check cache key
*/
private const CACHE_KEY = 'wc_tpp_update_info';
/**
* Default check frequency in hours
*/
private const DEFAULT_CHECK_FREQUENCY = 12;
/**
* Get singleton instance
*
* @return WC_TPP_Update_Checker
*/
public static function get_instance(): WC_TPP_Update_Checker {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
// Only register if updates are not disabled
if ($this->is_disabled()) {
return;
}
$this->register_hooks();
}
/**
* Check if auto-updates are disabled via constant
*
* @return bool
*/
private function is_disabled(): bool {
return defined('WC_TPP_DISABLE_AUTO_UPDATE') && WC_TPP_DISABLE_AUTO_UPDATE;
}
/**
* Register WordPress hooks
*/
private function register_hooks(): void {
// Check for updates
add_filter('pre_set_site_transient_update_plugins', array($this, 'check_for_updates'));
// Provide plugin information for update modal
add_filter('plugins_api', array($this, 'get_plugin_info'), 10, 3);
// Add authentication headers to download requests
add_filter('http_request_args', array($this, 'add_auth_headers'), 10, 2);
// Handle auto-install setting
add_filter('auto_update_plugin', array($this, 'handle_auto_install'), 10, 2);
// Clear cache when settings change
add_action('update_option_wc_tpp_license_key', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_license_server_url', array($this, 'clear_cache'));
add_action('update_option_wc_tpp_update_notification_enabled', array($this, 'clear_cache'));
}
/**
* Check if update notifications are enabled
*
* @return bool
*/
public static function is_update_notification_enabled(): bool {
return get_option('wc_tpp_update_notification_enabled', 'yes') === 'yes';
}
/**
* Check if auto-install is enabled
*
* @return bool
*/
public static function is_auto_install_enabled(): bool {
return get_option('wc_tpp_auto_install_enabled', 'no') === 'yes';
}
/**
* Get update check frequency in hours
*
* @return int
*/
public static function get_check_frequency(): int {
$frequency = (int) get_option('wc_tpp_update_check_frequency', self::DEFAULT_CHECK_FREQUENCY);
return max(1, min(168, $frequency)); // Clamp between 1 and 168 hours
}
/**
* Check for plugin updates
*
* @param object $transient WordPress update transient.
* @return object
*/
public function check_for_updates($transient) {
if (empty($transient->checked)) {
return $transient;
}
// Skip if notifications disabled
if (!self::is_update_notification_enabled()) {
return $transient;
}
// Get update info (cached)
$update_info = $this->get_update_info();
if (empty($update_info) || !$update_info['update_available']) {
return $transient;
}
// Build update object
$update_obj = $this->build_update_object($update_info);
if ($update_obj) {
$transient->response[WC_TPP_PLUGIN_BASENAME] = $update_obj;
}
return $transient;
}
/**
* Get plugin information for update modal
*
* @param false|object|array $result The result object or array.
* @param string $action The type of information being requested.
* @param object $args Plugin API arguments.
* @return false|object
*/
public function get_plugin_info($result, $action, $args) {
if ('plugin_information' !== $action) {
return $result;
}
if (!isset($args->slug) || self::PLUGIN_SLUG !== $args->slug) {
return $result;
}
// Get update info
$update_info = $this->get_update_info(true); // Force fetch for full info
if (empty($update_info)) {
return $result;
}
// Build plugin info object
return $this->build_plugin_info_object($update_info);
}
/**
* Add authentication headers to download requests
*
* @param array $args HTTP request arguments.
* @param string $url The request URL.
* @return array
*/
public function add_auth_headers(array $args, string $url): array {
$server_url = get_option('wc_tpp_license_server_url', '');
// Only add headers for requests to our license server
if (empty($server_url) || strpos($url, $server_url) !== 0) {
return $args;
}
$license_key = get_option('wc_tpp_license_key', '');
if (!empty($license_key)) {
$args['headers']['X-License-Key'] = $license_key;
}
return $args;
}
/**
* Handle auto-install setting
*
* @param bool|null $update Whether to auto-update.
* @param object $item The plugin update object.
* @return bool|null
*/
public function handle_auto_install($update, $item) {
// Check if this is our plugin
if (!isset($item->plugin) || WC_TPP_PLUGIN_BASENAME !== $item->plugin) {
return $update;
}
// Return our setting, or default behavior
if (self::is_auto_install_enabled()) {
return true;
}
return $update;
}
/**
* Get update info from cache or server
*
* @param bool $force_fetch Force fetching from server.
* @return array|null
*/
private function get_update_info(bool $force_fetch = false): ?array {
// Check cache first
if (!$force_fetch) {
$cached = get_transient(self::CACHE_KEY);
if (false !== $cached) {
return $cached;
}
}
// Fetch from server
$update_info = $this->fetch_update_info();
if ($update_info) {
// Cache the result
$cache_ttl = self::get_check_frequency() * HOUR_IN_SECONDS;
set_transient(self::CACHE_KEY, $update_info, $cache_ttl);
}
return $update_info;
}
/**
* Fetch update info from license server
*
* @return array|null
*/
private function fetch_update_info(): ?array {
$server_url = get_option('wc_tpp_license_server_url', '');
$license_key = get_option('wc_tpp_license_key', '');
$server_secret = get_option('wc_tpp_license_server_secret', '');
if (empty($server_url)) {
return null;
}
// Build the update check endpoint
$endpoint = trailingslashit($server_url) . 'wp-json/wc-licensed-product/v1/update-check';
// Get license checker for domain
$license_checker = WC_TPP_License_Checker::get_instance();
$domain = $license_checker->get_current_domain();
// Remove port for API call
$domain = preg_replace('/:[\d]+$/', '', $domain);
// Prepare request body
$body = array(
'license_key' => $license_key,
'domain' => $domain,
'plugin_slug' => self::PLUGIN_SLUG,
'current_version' => WC_TPP_VERSION,
);
// Make the request
$response = wp_remote_post($endpoint, array(
'timeout' => 15,
'headers' => array(
'Content-Type' => 'application/json',
'Accept' => 'application/json',
),
'body' => wp_json_encode($body),
));
if (is_wp_error($response)) {
return null;
}
$response_code = wp_remote_retrieve_response_code($response);
// Handle rate limiting
if (429 === $response_code) {
return null;
}
// Handle other errors
if ($response_code < 200 || $response_code >= 300) {
return null;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (empty($data) || !isset($data['success'])) {
return null;
}
return $data;
}
/**
* Build WordPress update object
*
* @param array $update_info Update information from server.
* @return object|null
*/
private function build_update_object(array $update_info): ?object {
if (empty($update_info['version'])) {
return null;
}
// Check if update is actually available
if (version_compare(WC_TPP_VERSION, $update_info['version'], '>=')) {
return null;
}
$obj = new \stdClass();
$obj->id = $update_info['id'] ?? self::PLUGIN_SLUG;
$obj->slug = $update_info['slug'] ?? self::PLUGIN_SLUG;
$obj->plugin = $update_info['plugin'] ?? WC_TPP_PLUGIN_BASENAME;
$obj->new_version = $update_info['version'];
$obj->url = $update_info['url'] ?? '';
$obj->package = $update_info['download_url'] ?? $update_info['package'] ?? '';
$obj->tested = $update_info['tested'] ?? '';
$obj->requires = $update_info['requires'] ?? '6.0';
$obj->requires_php = $update_info['requires_php'] ?? '8.3';
// Icons
if (!empty($update_info['icons'])) {
$obj->icons = $update_info['icons'];
}
// Banners
if (!empty($update_info['banners'])) {
$obj->banners = $update_info['banners'];
}
return $obj;
}
/**
* Build plugin info object for update modal
*
* @param array $update_info Update information from server.
* @return object
*/
private function build_plugin_info_object(array $update_info): object {
$obj = new \stdClass();
$obj->name = $update_info['name'] ?? 'WooCommerce Tier and Package Prices';
$obj->slug = $update_info['slug'] ?? self::PLUGIN_SLUG;
$obj->version = $update_info['version'] ?? WC_TPP_VERSION;
$obj->author = $update_info['author'] ?? '<a href="https://src.bundespruefstelle.ch/magdev">Marco Graetsch</a>';
$obj->author_profile = $update_info['author_profile'] ?? 'https://src.bundespruefstelle.ch/magdev';
$obj->homepage = $update_info['homepage'] ?? $update_info['url'] ?? '';
$obj->requires = $update_info['requires'] ?? '6.0';
$obj->tested = $update_info['tested'] ?? '';
$obj->requires_php = $update_info['requires_php'] ?? '8.3';
$obj->last_updated = $update_info['last_updated'] ?? '';
$obj->download_link = $update_info['download_url'] ?? $update_info['package'] ?? '';
// Sections (description, changelog, etc.)
$obj->sections = array();
if (!empty($update_info['sections']['description'])) {
$obj->sections['description'] = $update_info['sections']['description'];
} else {
$obj->sections['description'] = __('Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices.', 'wc-tier-package-prices');
}
if (!empty($update_info['sections']['changelog'])) {
$obj->sections['changelog'] = $update_info['sections']['changelog'];
} elseif (!empty($update_info['changelog'])) {
$obj->sections['changelog'] = $update_info['changelog'];
}
if (!empty($update_info['sections']['installation'])) {
$obj->sections['installation'] = $update_info['sections']['installation'];
}
// Icons
if (!empty($update_info['icons'])) {
$obj->icons = $update_info['icons'];
}
// Banners
if (!empty($update_info['banners'])) {
$obj->banners = $update_info['banners'];
}
return $obj;
}
/**
* Clear update cache
*/
public function clear_cache(): void {
delete_transient(self::CACHE_KEY);
// Also clear WordPress plugin update transient to force recheck
delete_site_transient('update_plugins');
}
/**
* Force check for updates
*
* @return array|null
*/
public function force_check(): ?array {
$this->clear_cache();
return $this->get_update_info(true);
}
/**
* Get the available update version if any
*
* @return string|null
*/
public function get_available_version(): ?string {
$update_info = $this->get_update_info();
if (empty($update_info) || empty($update_info['version'])) {
return null;
}
// Check if it's actually newer
if (version_compare(WC_TPP_VERSION, $update_info['version'], '>=')) {
return null;
}
return $update_info['version'];
}
/**
* Check if an update is available
*
* @return bool
*/
public function is_update_available(): bool {
return null !== $this->get_available_version();
}
}
}