You've already forked wc-tier-and-package-prices
- Upgraded from LicenseClient to SecureLicenseClient with HMAC-SHA256 response signature verification - Added Server Secret configuration field for secure communication - Added rate limit exception handling with retry time display - Added signature verification error handling - Added URL validation error handling (SSRF protection) - Updated all translation files with new strings - Compiled .mo files for all 7 language variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
530 lines
23 KiB
PHP
Executable File
530 lines
23 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* WooCommerce Settings Integration
|
|
*
|
|
* Adds Tier & Package Prices settings to WooCommerce Settings with sub-tabs
|
|
*
|
|
* @package WC_Tier_Package_Prices
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
if (!class_exists('WC_Settings_Page')) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* WC_TPP_Settings class
|
|
*/
|
|
if (!class_exists('WC_TPP_Settings')) {
|
|
class WC_TPP_Settings extends WC_Settings_Page {
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
$this->id = 'tier_package_prices';
|
|
$this->label = __('Tier & Package Prices', 'wc-tier-package-prices');
|
|
|
|
parent::__construct();
|
|
|
|
// 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'));
|
|
}
|
|
|
|
/**
|
|
* Get own sections - Modern WooCommerce pattern
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_own_sections() {
|
|
return array(
|
|
'' => __('General', 'wc-tier-package-prices'),
|
|
'license' => __('License', 'wc-tier-package-prices'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get settings for the default (General) section
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_settings_for_default_section() {
|
|
return array(
|
|
array(
|
|
'title' => __('Tier & Package Prices Settings', 'wc-tier-package-prices'),
|
|
'type' => 'title',
|
|
'desc' => __('Configure tier pricing and package pricing options for your WooCommerce products.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_settings',
|
|
),
|
|
|
|
array(
|
|
'title' => __('Enable Tier Pricing', 'wc-tier-package-prices'),
|
|
'desc' => __('Enable tier pricing for products', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_enable_tier_pricing',
|
|
'default' => 'yes',
|
|
'type' => 'checkbox',
|
|
'desc_tip' => __('Allow quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'),
|
|
),
|
|
|
|
array(
|
|
'title' => __('Enable Package Pricing', 'wc-tier-package-prices'),
|
|
'desc' => __('Enable fixed-price packages for products', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_enable_package_pricing',
|
|
'default' => 'yes',
|
|
'type' => 'checkbox',
|
|
'desc_tip' => __('Allow fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'),
|
|
),
|
|
|
|
array(
|
|
'title' => __('Display Pricing Table', 'wc-tier-package-prices'),
|
|
'desc' => __('Show tier and package pricing table on product pages', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_display_table',
|
|
'default' => 'yes',
|
|
'type' => 'checkbox',
|
|
'desc_tip' => __('Display the pricing table to customers on product pages.', 'wc-tier-package-prices'),
|
|
),
|
|
|
|
array(
|
|
'title' => __('Display Position', 'wc-tier-package-prices'),
|
|
'desc' => __('Choose where to display the pricing table on product pages.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_display_position',
|
|
'default' => 'after_add_to_cart',
|
|
'type' => 'select',
|
|
'class' => 'wc-enhanced-select',
|
|
'css' => 'min-width:300px;',
|
|
'desc_tip' => true,
|
|
'options' => array(
|
|
'before_add_to_cart' => __('Before Add to Cart Button', 'wc-tier-package-prices'),
|
|
'after_add_to_cart' => __('After Add to Cart Button', 'wc-tier-package-prices'),
|
|
'after_price' => __('After Price', 'wc-tier-package-prices'),
|
|
),
|
|
),
|
|
|
|
array(
|
|
'title' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
|
|
'desc' => __('Limit quantities to defined package sizes only', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_restrict_package_quantities',
|
|
'default' => 'no',
|
|
'type' => 'checkbox',
|
|
'desc_tip' => __('When enabled, customers can only purchase products in the exact quantities defined in packages. The quantity input field will be hidden and replaced with package selection buttons.', 'wc-tier-package-prices'),
|
|
),
|
|
|
|
array(
|
|
'type' => 'sectionend',
|
|
'id' => 'wc_tpp_settings',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get settings for the License section
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_settings_for_license_section() {
|
|
return array(
|
|
array(
|
|
'title' => __('License Management', 'wc-tier-package-prices'),
|
|
'type' => 'title',
|
|
'desc' => __('Enter your license key to receive updates and support.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_license_settings',
|
|
),
|
|
|
|
array(
|
|
'title' => __('License Server URL', 'wc-tier-package-prices'),
|
|
'desc' => __('The URL of the license server.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_license_server_url',
|
|
'type' => 'url',
|
|
'default' => '',
|
|
'css' => 'min-width:400px;',
|
|
'desc_tip' => true,
|
|
),
|
|
|
|
array(
|
|
'title' => __('License Key', 'wc-tier-package-prices'),
|
|
'desc' => __('Your license key for this plugin.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_license_key',
|
|
'type' => 'text',
|
|
'default' => '',
|
|
'css' => 'min-width:400px;',
|
|
'desc_tip' => true,
|
|
),
|
|
|
|
array(
|
|
'title' => __('Server Secret', 'wc-tier-package-prices'),
|
|
'desc' => __('The shared secret for secure communication with the license server.', 'wc-tier-package-prices'),
|
|
'id' => 'wc_tpp_license_server_secret',
|
|
'type' => 'password',
|
|
'default' => '',
|
|
'css' => 'min-width:400px;',
|
|
'desc_tip' => true,
|
|
),
|
|
|
|
array(
|
|
'title' => __('License Status', 'wc-tier-package-prices'),
|
|
'type' => 'wc_tpp_license_status',
|
|
'id' => 'wc_tpp_license_status_display',
|
|
),
|
|
|
|
array(
|
|
'type' => 'sectionend',
|
|
'id' => 'wc_tpp_license_settings',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get cached license status
|
|
*
|
|
* @return array|false
|
|
*/
|
|
private function get_cached_license_status() {
|
|
return get_transient('wc_tpp_license_status');
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for license validation
|
|
*/
|
|
public function ajax_validate_license() {
|
|
check_ajax_referer('wc_tpp_license_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
|
|
}
|
|
|
|
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
|
|
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
|
|
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
|
|
|
|
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
|
|
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
|
|
}
|
|
|
|
try {
|
|
$client = $this->get_license_client($server_url, $server_secret);
|
|
$domain = $this->get_current_domain();
|
|
$result = $client->validate($license_key, $domain);
|
|
|
|
// Cache the status
|
|
set_transient('wc_tpp_license_status', 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'),
|
|
), DAY_IN_SECONDS);
|
|
|
|
wp_send_json_success(array(
|
|
'message' => __('License is valid!', 'wc-tier-package-prices'),
|
|
'status' => $this->get_cached_license_status(),
|
|
));
|
|
|
|
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => sprintf(
|
|
/* translators: %d: Number of seconds to wait */
|
|
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
|
|
$e->retryAfter ?? 60
|
|
),
|
|
'code' => 'rate_limit_exceeded',
|
|
'retry_after' => $e->retryAfter,
|
|
));
|
|
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
|
|
delete_transient('wc_tpp_license_status');
|
|
wp_send_json_error(array(
|
|
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
|
|
'code' => 'signature_error',
|
|
));
|
|
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
|
|
delete_transient('wc_tpp_license_status');
|
|
wp_send_json_error(array(
|
|
'message' => $e->getMessage(),
|
|
'code' => $e->errorCode ?? 'unknown',
|
|
));
|
|
} catch (\InvalidArgumentException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => $e->getMessage(),
|
|
'code' => 'invalid_url',
|
|
));
|
|
} catch (\Exception $e) {
|
|
delete_transient('wc_tpp_license_status');
|
|
wp_send_json_error(array(
|
|
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
|
|
'code' => 'exception',
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for license activation
|
|
*/
|
|
public function ajax_activate_license() {
|
|
check_ajax_referer('wc_tpp_license_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
wp_send_json_error(array('message' => __('Permission denied.', 'wc-tier-package-prices')));
|
|
}
|
|
|
|
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
|
|
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
|
|
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
|
|
|
|
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
|
|
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
|
|
}
|
|
|
|
try {
|
|
$client = $this->get_license_client($server_url, $server_secret);
|
|
$domain = $this->get_current_domain();
|
|
$result = $client->activate($license_key, $domain);
|
|
|
|
if ($result->success) {
|
|
// Validate to get full status after activation
|
|
$validate_result = $client->validate($license_key, $domain);
|
|
|
|
set_transient('wc_tpp_license_status', array(
|
|
'valid' => true,
|
|
'product_id' => $validate_result->productId,
|
|
'expires_at' => $validate_result->expiresAt?->format('Y-m-d H:i:s'),
|
|
'is_lifetime' => $validate_result->isLifetime(),
|
|
'checked_at' => current_time('mysql'),
|
|
), DAY_IN_SECONDS);
|
|
|
|
wp_send_json_success(array(
|
|
'message' => __('License activated successfully!', 'wc-tier-package-prices'),
|
|
'status' => $this->get_cached_license_status(),
|
|
));
|
|
}
|
|
|
|
wp_send_json_error(array('message' => $result->message));
|
|
|
|
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => sprintf(
|
|
/* translators: %d: Number of seconds to wait */
|
|
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
|
|
$e->retryAfter ?? 60
|
|
),
|
|
'code' => 'rate_limit_exceeded',
|
|
'retry_after' => $e->retryAfter,
|
|
));
|
|
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
|
|
'code' => 'signature_error',
|
|
));
|
|
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => $e->getMessage(),
|
|
'code' => $e->errorCode ?? 'unknown',
|
|
));
|
|
} catch (\InvalidArgumentException $e) {
|
|
wp_send_json_error(array(
|
|
'message' => $e->getMessage(),
|
|
'code' => 'invalid_url',
|
|
));
|
|
} catch (\Exception $e) {
|
|
wp_send_json_error(array(
|
|
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
|
|
'code' => 'exception',
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get license client instance
|
|
*
|
|
* Uses SecureLicenseClient for HMAC signature verification.
|
|
*
|
|
* @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,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get current domain for license validation
|
|
*
|
|
* @return string
|
|
*/
|
|
private function get_current_domain(): string {
|
|
return wp_parse_url(home_url(), PHP_URL_HOST);
|
|
}
|
|
|
|
/**
|
|
* Output the settings
|
|
*/
|
|
public function output() {
|
|
global $current_section;
|
|
|
|
// Register custom field type for license status display
|
|
add_action('woocommerce_admin_field_wc_tpp_license_status', array($this, 'output_license_status_field'));
|
|
|
|
parent::output();
|
|
|
|
// Add JavaScript for license section
|
|
if ('license' === $current_section) {
|
|
$this->output_license_scripts();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output license status custom field
|
|
*
|
|
* @param array $value Field configuration.
|
|
*/
|
|
public function output_license_status_field($value) {
|
|
$status = $this->get_cached_license_status();
|
|
?>
|
|
<tr valign="top">
|
|
<th scope="row" class="titledesc">
|
|
<label><?php esc_html_e('License Status', 'wc-tier-package-prices'); ?></label>
|
|
</th>
|
|
<td class="forminp">
|
|
<div id="wc-tpp-license-status-container" class="<?php echo !empty($status['valid']) ? 'valid' : 'invalid'; ?>">
|
|
<?php $this->render_license_status_html($status); ?>
|
|
</div>
|
|
<p class="description" style="margin-top: 10px;">
|
|
<button type="button" class="button" id="wc-tpp-validate-license">
|
|
<?php esc_html_e('Validate License', 'wc-tier-package-prices'); ?>
|
|
</button>
|
|
<button type="button" class="button" id="wc-tpp-activate-license">
|
|
<?php esc_html_e('Activate License', 'wc-tier-package-prices'); ?>
|
|
</button>
|
|
<span class="spinner" id="wc-tpp-license-spinner"></span>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render license status HTML
|
|
*
|
|
* @param array|false $status License status data.
|
|
*/
|
|
private function render_license_status_html($status) {
|
|
if (empty($status)) {
|
|
echo '<span class="wc-tpp-license-inactive">' . esc_html__('No license activated', 'wc-tier-package-prices') . '</span>';
|
|
return;
|
|
}
|
|
|
|
if (!empty($status['valid'])) {
|
|
echo '<span class="wc-tpp-license-active">' . esc_html__('License Active', 'wc-tier-package-prices') . '</span>';
|
|
if (!empty($status['expires_at']) && empty($status['is_lifetime'])) {
|
|
echo '<br><small>' . sprintf(
|
|
/* translators: %s: Expiration date */
|
|
esc_html__('Expires: %s', 'wc-tier-package-prices'),
|
|
esc_html($status['expires_at'])
|
|
) . '</small>';
|
|
} elseif (!empty($status['is_lifetime'])) {
|
|
echo '<br><small>' . esc_html__('Lifetime License', 'wc-tier-package-prices') . '</small>';
|
|
}
|
|
if (!empty($status['checked_at'])) {
|
|
echo '<br><small>' . sprintf(
|
|
/* translators: %s: Last check timestamp */
|
|
esc_html__('Last checked: %s', 'wc-tier-package-prices'),
|
|
esc_html($status['checked_at'])
|
|
) . '</small>';
|
|
}
|
|
} else {
|
|
echo '<span class="wc-tpp-license-inactive">' . esc_html__('License Invalid', 'wc-tier-package-prices') . '</span>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output JavaScript for license management
|
|
*/
|
|
private function output_license_scripts() {
|
|
$nonce = wp_create_nonce('wc_tpp_license_nonce');
|
|
?>
|
|
<script type="text/javascript">
|
|
jQuery(function($) {
|
|
var $validateBtn = $('#wc-tpp-validate-license');
|
|
var $activateBtn = $('#wc-tpp-activate-license');
|
|
var $spinner = $('#wc-tpp-license-spinner');
|
|
|
|
function getLicenseData() {
|
|
return {
|
|
license_key: $('#wc_tpp_license_key').val(),
|
|
server_url: $('#wc_tpp_license_server_url').val(),
|
|
server_secret: $('#wc_tpp_license_server_secret').val(),
|
|
nonce: '<?php echo esc_js($nonce); ?>'
|
|
};
|
|
}
|
|
|
|
function showSpinner() {
|
|
$spinner.addClass('is-active');
|
|
$validateBtn.prop('disabled', true);
|
|
$activateBtn.prop('disabled', true);
|
|
}
|
|
|
|
function hideSpinner() {
|
|
$spinner.removeClass('is-active');
|
|
$validateBtn.prop('disabled', false);
|
|
$activateBtn.prop('disabled', false);
|
|
}
|
|
|
|
$validateBtn.on('click', function() {
|
|
var data = getLicenseData();
|
|
if (!data.license_key || !data.server_url || !data.server_secret) {
|
|
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
|
|
return;
|
|
}
|
|
|
|
showSpinner();
|
|
$.post(ajaxurl, $.extend({action: 'wc_tpp_validate_license'}, data))
|
|
.done(function(response) {
|
|
if (response.success) {
|
|
alert(response.data.message);
|
|
location.reload();
|
|
} else {
|
|
alert(response.data.message || '<?php echo esc_js(__('Validation failed.', 'wc-tier-package-prices')); ?>');
|
|
}
|
|
})
|
|
.fail(function() {
|
|
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
|
|
})
|
|
.always(hideSpinner);
|
|
});
|
|
|
|
$activateBtn.on('click', function() {
|
|
var data = getLicenseData();
|
|
if (!data.license_key || !data.server_url || !data.server_secret) {
|
|
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
|
|
return;
|
|
}
|
|
|
|
showSpinner();
|
|
$.post(ajaxurl, $.extend({action: 'wc_tpp_activate_license'}, data))
|
|
.done(function(response) {
|
|
if (response.success) {
|
|
alert(response.data.message);
|
|
location.reload();
|
|
} else {
|
|
alert(response.data.message || '<?php echo esc_js(__('Activation failed.', 'wc-tier-package-prices')); ?>');
|
|
}
|
|
})
|
|
.fail(function() {
|
|
alert('<?php echo esc_js(__('Request failed. Please try again.', 'wc-tier-package-prices')); ?>');
|
|
})
|
|
.always(hideSpinner);
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
}
|
|
}
|