You've already forked wc-licensed-product
v0.3.0 - Self-Licensing: - Add PluginLicenseChecker singleton for license validation - Integrate magdev/wc-licensed-product-client library - Add license settings: server URL, key, optional secret - Disable frontend features without valid license (except localhost) - Add license status display with verify button in settings v0.3.1 - Settings UI Improvements: - Reorganize settings page with WooCommerce-style sub-tabs - Split settings into: Plugin License, Default Settings, Notifications - Use PHP 8 match expression for section-specific rendering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
474 lines
17 KiB
PHP
474 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Settings Controller
|
|
*
|
|
* @package Jeremias\WcLicensedProduct\Admin
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Jeremias\WcLicensedProduct\Admin;
|
|
|
|
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
|
|
|
|
/**
|
|
* Handles WooCommerce settings tab for license defaults
|
|
*/
|
|
final class SettingsController
|
|
{
|
|
/**
|
|
* Settings option name
|
|
*/
|
|
public const OPTION_NAME = 'wc_licensed_product_settings';
|
|
|
|
/**
|
|
* Tab ID
|
|
*/
|
|
private const TAB_ID = 'licensed_product';
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->registerHooks();
|
|
}
|
|
|
|
/**
|
|
* Register WordPress hooks
|
|
*/
|
|
private function registerHooks(): void
|
|
{
|
|
add_filter('woocommerce_settings_tabs_array', [$this, 'addSettingsTab'], 50);
|
|
add_action('woocommerce_sections_' . self::TAB_ID, [$this, 'outputSections']);
|
|
add_action('woocommerce_settings_' . self::TAB_ID, [$this, 'renderSettingsTab']);
|
|
add_action('woocommerce_update_options_' . self::TAB_ID, [$this, 'saveSettings']);
|
|
add_action('wp_ajax_wclp_verify_plugin_license', [$this, 'handleVerifyLicense']);
|
|
}
|
|
|
|
/**
|
|
* Add settings tab to WooCommerce settings
|
|
*/
|
|
public function addSettingsTab(array $tabs): array
|
|
{
|
|
$tabs[self::TAB_ID] = __('Licensed Products', 'wc-licensed-product');
|
|
return $tabs;
|
|
}
|
|
|
|
/**
|
|
* Get available sections
|
|
*/
|
|
public function getSections(): array
|
|
{
|
|
return [
|
|
'' => __('Plugin License', 'wc-licensed-product'),
|
|
'defaults' => __('Default Settings', 'wc-licensed-product'),
|
|
'notifications' => __('Notifications', 'wc-licensed-product'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get current section from URL
|
|
*/
|
|
private function getCurrentSection(): string
|
|
{
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
return isset($_GET['section']) ? sanitize_title(wp_unslash($_GET['section'])) : '';
|
|
}
|
|
|
|
/**
|
|
* Output sections navigation (sub-tabs)
|
|
*/
|
|
public function outputSections(): void
|
|
{
|
|
$sections = $this->getSections();
|
|
|
|
if (empty($sections) || count($sections) <= 1) {
|
|
return;
|
|
}
|
|
|
|
$currentSection = $this->getCurrentSection();
|
|
|
|
echo '<ul class="subsubsub">';
|
|
|
|
$arrayKeys = array_keys($sections);
|
|
|
|
foreach ($sections as $id => $label) {
|
|
$url = admin_url('admin.php?page=wc-settings&tab=' . self::TAB_ID . '§ion=' . sanitize_title($id));
|
|
$class = ($currentSection === $id) ? 'current' : '';
|
|
$separator = (end($arrayKeys) === $id) ? '' : ' | ';
|
|
|
|
echo '<li><a href="' . esc_url($url) . '" class="' . esc_attr($class) . '">' . esc_html($label) . '</a>' . $separator . '</li>';
|
|
}
|
|
|
|
echo '</ul><br class="clear" />';
|
|
}
|
|
|
|
/**
|
|
* Get settings fields for the current section
|
|
*/
|
|
public function getSettingsFields(): array
|
|
{
|
|
$currentSection = $this->getCurrentSection();
|
|
|
|
return match ($currentSection) {
|
|
'defaults' => $this->getDefaultsSettings(),
|
|
'notifications' => $this->getNotificationsSettings(),
|
|
default => $this->getPluginLicenseSettings(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get plugin license settings (default section)
|
|
*/
|
|
private function getPluginLicenseSettings(): array
|
|
{
|
|
return [
|
|
'plugin_license_section_title' => [
|
|
'name' => __('Plugin License', 'wc-licensed-product'),
|
|
'type' => 'title',
|
|
'desc' => __('Configure the license for this plugin. A valid license is required for frontend features to work.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_section_plugin_license',
|
|
],
|
|
'plugin_license_server_url' => [
|
|
'name' => __('License Server URL', 'wc-licensed-product'),
|
|
'type' => 'url',
|
|
'desc' => __('The URL of the license server (e.g., https://shop.example.com).', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_plugin_license_server_url',
|
|
'default' => '',
|
|
'placeholder' => 'https://shop.example.com',
|
|
],
|
|
'plugin_license_key' => [
|
|
'name' => __('License Key', 'wc-licensed-product'),
|
|
'type' => 'text',
|
|
'desc' => __('Your license key in XXXX-XXXX-XXXX-XXXX format.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_plugin_license_key',
|
|
'default' => '',
|
|
'placeholder' => 'XXXX-XXXX-XXXX-XXXX',
|
|
],
|
|
'plugin_license_server_secret' => [
|
|
'name' => __('Server Secret (Optional)', 'wc-licensed-product'),
|
|
'type' => 'password',
|
|
'desc' => __('If the license server uses signed responses, enter the shared secret here for enhanced security.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_plugin_license_server_secret',
|
|
'default' => '',
|
|
],
|
|
'plugin_license_section_end' => [
|
|
'type' => 'sectionend',
|
|
'id' => 'wc_licensed_product_section_plugin_license_end',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get default license settings
|
|
*/
|
|
private function getDefaultsSettings(): array
|
|
{
|
|
return [
|
|
'section_title' => [
|
|
'name' => __('Default License Settings', 'wc-licensed-product'),
|
|
'type' => 'title',
|
|
'desc' => __('These settings serve as defaults for new licensed products. Individual product settings override these defaults.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_section_defaults',
|
|
],
|
|
'default_max_activations' => [
|
|
'name' => __('Default Max Activations', 'wc-licensed-product'),
|
|
'type' => 'number',
|
|
'desc' => __('Default maximum number of domain activations per license.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_default_max_activations',
|
|
'default' => '1',
|
|
'custom_attributes' => [
|
|
'min' => '1',
|
|
'step' => '1',
|
|
],
|
|
],
|
|
'default_validity_days' => [
|
|
'name' => __('Default License Validity (Days)', 'wc-licensed-product'),
|
|
'type' => 'number',
|
|
'desc' => __('Default number of days a license is valid. Leave empty or set to 0 for lifetime licenses.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_default_validity_days',
|
|
'default' => '',
|
|
'placeholder' => __('Lifetime', 'wc-licensed-product'),
|
|
'custom_attributes' => [
|
|
'min' => '0',
|
|
'step' => '1',
|
|
],
|
|
],
|
|
'default_bind_to_version' => [
|
|
'name' => __('Default Bind to Major Version', 'wc-licensed-product'),
|
|
'type' => 'checkbox',
|
|
'desc' => __('If enabled, licenses are bound to the major version at purchase time by default.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_default_bind_to_version',
|
|
'default' => 'no',
|
|
],
|
|
'section_end' => [
|
|
'type' => 'sectionend',
|
|
'id' => 'wc_licensed_product_section_defaults_end',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get notifications settings
|
|
*/
|
|
private function getNotificationsSettings(): array
|
|
{
|
|
return [
|
|
'email_section_title' => [
|
|
'name' => __('Expiration Warning Schedule', 'wc-licensed-product'),
|
|
'type' => 'title',
|
|
'desc' => sprintf(
|
|
/* translators: %s: URL to WooCommerce email settings */
|
|
__('Configure when expiration warning emails are sent. To customize the email template, enable/disable, or change the subject, go to %s.', 'wc-licensed-product'),
|
|
'<a href="' . esc_url(admin_url('admin.php?page=wc-settings&tab=email§ion=wclp_license_expiration')) . '">' .
|
|
__('WooCommerce > Settings > Emails > License Expiration Warning', 'wc-licensed-product') . '</a>'
|
|
),
|
|
'id' => 'wc_licensed_product_section_email',
|
|
],
|
|
'expiration_warning_days_first' => [
|
|
'name' => __('First Warning (Days Before)', 'wc-licensed-product'),
|
|
'type' => 'number',
|
|
'desc' => __('Days before expiration to send the first warning email.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_expiration_warning_days_first',
|
|
'default' => '7',
|
|
'custom_attributes' => [
|
|
'min' => '1',
|
|
'step' => '1',
|
|
],
|
|
],
|
|
'expiration_warning_days_second' => [
|
|
'name' => __('Second Warning (Days Before)', 'wc-licensed-product'),
|
|
'type' => 'number',
|
|
'desc' => __('Days before expiration to send the second warning email. Set to 0 to disable.', 'wc-licensed-product'),
|
|
'id' => 'wc_licensed_product_expiration_warning_days_second',
|
|
'default' => '1',
|
|
'custom_attributes' => [
|
|
'min' => '0',
|
|
'step' => '1',
|
|
],
|
|
],
|
|
'email_section_end' => [
|
|
'type' => 'sectionend',
|
|
'id' => 'wc_licensed_product_section_email_end',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Render settings tab content
|
|
*/
|
|
public function renderSettingsTab(): void
|
|
{
|
|
$currentSection = $this->getCurrentSection();
|
|
|
|
// Only show license status on the plugin license section
|
|
if ($currentSection === '') {
|
|
$this->renderLicenseStatus();
|
|
}
|
|
|
|
woocommerce_admin_fields($this->getSettingsFields());
|
|
}
|
|
|
|
/**
|
|
* Render license status notice
|
|
*/
|
|
private function renderLicenseStatus(): void
|
|
{
|
|
$checker = PluginLicenseChecker::getInstance();
|
|
|
|
if ($checker->isLocalhost()) {
|
|
echo '<div class="notice notice-info inline"><p>';
|
|
echo '<span class="dashicons dashicons-info" style="color: #00a0d2;"></span> ';
|
|
echo esc_html__('Running on localhost - license validation bypassed.', 'wc-licensed-product');
|
|
echo '</p></div>';
|
|
return;
|
|
}
|
|
|
|
if ($checker->isLicenseValid()) {
|
|
echo '<div class="notice notice-success inline"><p>';
|
|
echo '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ';
|
|
echo esc_html__('License is valid and active.', 'wc-licensed-product');
|
|
echo '</p></div>';
|
|
} else {
|
|
$error = $checker->getLastError();
|
|
echo '<div class="notice notice-error inline"><p>';
|
|
echo '<span class="dashicons dashicons-warning" style="color: #dc3232;"></span> ';
|
|
echo esc_html__('License is not valid. Frontend features are disabled.', 'wc-licensed-product');
|
|
if ($error) {
|
|
echo '<br><small>' . esc_html($error) . '</small>';
|
|
}
|
|
echo '</p></div>';
|
|
}
|
|
|
|
// Add verify button
|
|
$nonce = wp_create_nonce('wclp_verify_license');
|
|
echo '<p>';
|
|
echo '<button type="button" class="button" id="wclp-verify-license" data-nonce="' . esc_attr($nonce) . '">';
|
|
echo esc_html__('Verify License', 'wc-licensed-product');
|
|
echo '</button>';
|
|
echo '<span id="wclp-verify-result" style="margin-left: 10px;"></span>';
|
|
echo '</p>';
|
|
|
|
// Inline script for verify button
|
|
?>
|
|
<script type="text/javascript">
|
|
jQuery(function($) {
|
|
$('#wclp-verify-license').on('click', function() {
|
|
var $btn = $(this);
|
|
var $result = $('#wclp-verify-result');
|
|
var nonce = $btn.data('nonce');
|
|
|
|
$btn.prop('disabled', true).text('<?php echo esc_js(__('Verifying...', 'wc-licensed-product')); ?>');
|
|
$result.text('');
|
|
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'wclp_verify_plugin_license',
|
|
nonce: nonce
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$result.html('<span style="color: #46b450;">' + response.data.message + '</span>');
|
|
location.reload();
|
|
} else {
|
|
$result.html('<span style="color: #dc3232;">' + response.data.message + '</span>');
|
|
}
|
|
},
|
|
error: function() {
|
|
$result.html('<span style="color: #dc3232;"><?php echo esc_js(__('Request failed.', 'wc-licensed-product')); ?></span>');
|
|
},
|
|
complete: function() {
|
|
$btn.prop('disabled', false).text('<?php echo esc_js(__('Verify License', 'wc-licensed-product')); ?>');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Save settings
|
|
*/
|
|
public function saveSettings(): void
|
|
{
|
|
woocommerce_update_options($this->getSettingsFields());
|
|
}
|
|
|
|
/**
|
|
* Get default max activations
|
|
*/
|
|
public static function getDefaultMaxActivations(): int
|
|
{
|
|
$value = get_option('wc_licensed_product_default_max_activations', 1);
|
|
return max(1, (int) $value);
|
|
}
|
|
|
|
/**
|
|
* Get default validity days
|
|
*/
|
|
public static function getDefaultValidityDays(): ?int
|
|
{
|
|
$value = get_option('wc_licensed_product_default_validity_days', '');
|
|
if ($value === '' || $value === '0') {
|
|
return null;
|
|
}
|
|
return (int) $value;
|
|
}
|
|
|
|
/**
|
|
* Get default bind to version setting
|
|
*/
|
|
public static function getDefaultBindToVersion(): bool
|
|
{
|
|
return get_option('wc_licensed_product_default_bind_to_version', 'no') === 'yes';
|
|
}
|
|
|
|
/**
|
|
* Check if expiration warning emails are enabled
|
|
* This checks both the WooCommerce email setting and the old setting for backwards compatibility
|
|
*/
|
|
public static function isExpirationEmailsEnabled(): bool
|
|
{
|
|
// Check WooCommerce email enabled status
|
|
$emailEnabled = get_option('woocommerce_wclp_license_expiration_settings');
|
|
if (is_array($emailEnabled) && isset($emailEnabled['enabled'])) {
|
|
return $emailEnabled['enabled'] === 'yes';
|
|
}
|
|
// Default to enabled if not yet configured
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get first warning days before expiration
|
|
*/
|
|
public static function getFirstWarningDays(): int
|
|
{
|
|
$value = get_option('wc_licensed_product_expiration_warning_days_first', 7);
|
|
return max(1, (int) $value);
|
|
}
|
|
|
|
/**
|
|
* Get second warning days before expiration
|
|
*/
|
|
public static function getSecondWarningDays(): int
|
|
{
|
|
$value = get_option('wc_licensed_product_expiration_warning_days_second', 1);
|
|
return max(0, (int) $value);
|
|
}
|
|
|
|
/**
|
|
* Get plugin license server URL
|
|
*/
|
|
public static function getPluginLicenseServerUrl(): string
|
|
{
|
|
return (string) get_option('wc_licensed_product_plugin_license_server_url', '');
|
|
}
|
|
|
|
/**
|
|
* Get plugin license key
|
|
*/
|
|
public static function getPluginLicenseKey(): string
|
|
{
|
|
return (string) get_option('wc_licensed_product_plugin_license_key', '');
|
|
}
|
|
|
|
/**
|
|
* Get plugin license server secret
|
|
*/
|
|
public static function getPluginLicenseServerSecret(): ?string
|
|
{
|
|
$secret = get_option('wc_licensed_product_plugin_license_server_secret', '');
|
|
return !empty($secret) ? (string) $secret : null;
|
|
}
|
|
|
|
/**
|
|
* Handle AJAX verify license request
|
|
*/
|
|
public function handleVerifyLicense(): void
|
|
{
|
|
if (!check_ajax_referer('wclp_verify_license', 'nonce', false)) {
|
|
wp_send_json_error(['message' => __('Security check failed.', 'wc-licensed-product')], 403);
|
|
}
|
|
|
|
if (!current_user_can('manage_woocommerce')) {
|
|
wp_send_json_error(['message' => __('Insufficient permissions.', 'wc-licensed-product')], 403);
|
|
}
|
|
|
|
$checker = PluginLicenseChecker::getInstance();
|
|
$checker->clearCache();
|
|
|
|
$valid = $checker->validateLicense(true);
|
|
|
|
if ($valid) {
|
|
wp_send_json_success(['message' => __('License verified successfully!', 'wc-licensed-product')]);
|
|
} else {
|
|
$error = $checker->getLastError() ?: __('License validation failed.', 'wc-licensed-product');
|
|
wp_send_json_error(['message' => $error]);
|
|
}
|
|
}
|
|
}
|