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 '
'; } /** * 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'), '' . __('WooCommerce > Settings > Emails > License Expiration Warning', 'wc-licensed-product') . '' ), '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 '

'; echo ' '; echo esc_html__('Running on localhost - license validation bypassed.', 'wc-licensed-product'); echo '

'; return; } if ($checker->isLicenseValid()) { echo '

'; echo ' '; echo esc_html__('License is valid and active.', 'wc-licensed-product'); echo '

'; } else { $error = $checker->getLastError(); echo '

'; echo ' '; echo esc_html__('License is not valid. Frontend features are disabled.', 'wc-licensed-product'); if ($error) { echo '
' . esc_html($error) . ''; } echo '

'; } // Add verify button $nonce = wp_create_nonce('wclp_verify_license'); echo '

'; echo ''; echo ''; echo '

'; // Inline script for verify button ?> 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]); } } }