diff --git a/CHANGELOG.md b/CHANGELOG.md index a961a78..da31509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,45 @@ All notable changes to WooCommerce Tier and Package Prices will be documented in The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2026-01-25 + +### Breaking Changes + +- **PHP 8.3 Required**: Minimum PHP version increased from 7.4 to 8.3 to support modern dependencies and the license client library. Users on older PHP versions will see an admin notice and the plugin will not load. + +### Added + +- **License Management**: Integrated `magdev/wc-licensed-product-client` library for license validation and activation + - New "License" settings tab for entering license server URL and license key + - License validation and activation via AJAX with visual feedback + - License status display showing active/inactive state, expiration date, and last check time + - Cached license status with daily auto-refresh + +- **Settings Page Sub-tabs**: Split the settings page into "General" and "License" tabs using modern WooCommerce patterns + - Refactored to use `get_own_sections()` and `get_settings_for_{section}_section()` methods + - Improved navigation and organization of settings + +- **PHP Version Check**: Added runtime PHP version validation with admin notice for incompatible servers + +### Changed + +- Updated composer.json to require PHP 8.3+ and added `magdev/wc-licensed-product-client` dependency +- Settings class now uses modern WooCommerce settings API patterns + +### Technical Details + +**New Dependencies**: +- `magdev/wc-licensed-product-client: ^0.1` (from private repository) +- `symfony/http-client: ^7.0` (transitive) +- `psr/log: ^3.0`, `psr/cache: ^3.0`, `psr/http-client: ^1.0` (transitive) + +**License Client Integration**: +- Uses `LicenseClient` class for API communication +- AJAX endpoints: `wc_tpp_validate_license`, `wc_tpp_activate_license` +- License status cached in WordPress transient (`wc_tpp_license_status`) + +--- + ## [1.2.9] - 2025-12-30 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index cba92e7..eb7e6ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # WooCommerce Tier and Package Prices - AI Context Document -**Last Updated:** 2025-12-30 -**Current Version:** 1.2.9 +**Last Updated:** 2026-01-25 +**Current Version:** 1.3.0 **Author:** Marco Graetsch **Project Status:** Production-ready WordPress plugin @@ -18,16 +18,15 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w ## Temporary Roadmap -**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. +**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. Always keep the `Known Bugs` section and create a section with the next bugfix and minor version after a release. -### Version 1.3.0 +### Version 1.3.1 -- Implement `https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/raw/branch/main/docs/client-implementation.md`. Add the license management to the settings page -- Split the settings page into sub-tabs, like in the WooCommerce Advanced tab +- TBD ## Technical Stack -- **Language:** PHP 7.4+ +- **Language:** PHP 8.3+ - **Framework:** WordPress Plugin API - **E-commerce:** WooCommerce 8.0+ (tested up to 10.x) - **Template Engine:** Twig 3.0 (via Composer) @@ -41,8 +40,11 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w ```json { "twig/twig": "^3.0", - "symfony/polyfill-ctype": "^1.x", - "symfony/polyfill-mbstring": "^1.x" + "magdev/wc-licensed-product-client": "^0.1", + "symfony/http-client": "^7.0", + "psr/log": "^3.0", + "psr/cache": "^3.0", + "psr/http-client": "^1.0" } ``` diff --git a/assets/css/admin.css b/assets/css/admin.css index d211170..c190261 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -154,3 +154,52 @@ label[for^="wc_tpp_restrict_to_packages_"] { input[id^="wc_tpp_restrict_to_packages_"] + .description { display: none; } + +/* License Status Styling */ +.wc-tpp-license-active { + color: #46b450; + font-weight: 600; +} + +.wc-tpp-license-inactive { + color: #dc3232; + font-weight: 600; +} + +.wc-tpp-license-expired { + color: #ffb900; + font-weight: 600; +} + +#wc-tpp-license-spinner { + float: none; + margin-top: 0; + vertical-align: middle; +} + +#wc-tpp-validate-license, +#wc-tpp-activate-license { + margin-right: 8px; +} + +#wc-tpp-license-status-container { + margin-bottom: 10px; + padding: 10px 15px; + background: #f9f9f9; + border-left: 4px solid #ccc; + border-radius: 2px; +} + +#wc-tpp-license-status-container.valid { + border-left-color: #46b450; + background: #f0fff0; +} + +#wc-tpp-license-status-container.invalid { + border-left-color: #dc3232; + background: #fff0f0; +} + +#wc-tpp-license-status-container small { + color: #666; +} diff --git a/composer.json b/composer.json index f869819..ea78249 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "magdev/wc-tier-package-prices", "description": "WooCommerce plugin for tier pricing and package prices with Twig templates", - "version": "1.2.9", + "version": "1.3.0", "type": "wordpress-plugin", "license": "GPL-2.0-or-later", "authors": [ @@ -10,9 +10,16 @@ "homepage": "https://src.bundespruefstelle.ch/magdev" } ], + "repositories": [ + { + "type": "vcs", + "url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git" + } + ], "require": { - "php": ">=7.4", - "twig/twig": "^3.0" + "php": ">=8.3", + "twig/twig": "^3.0", + "magdev/wc-licensed-product-client": "^0.1" }, "autoload": { "classmap": [ diff --git a/includes/class-wc-tpp-settings.php b/includes/class-wc-tpp-settings.php old mode 100644 new mode 100755 index 5cfdd12..c962cf8 --- a/includes/class-wc-tpp-settings.php +++ b/includes/class-wc-tpp-settings.php @@ -2,7 +2,7 @@ /** * WooCommerce Settings Integration * - * Adds Tier & Package Prices settings to WooCommerce Settings > Advanced tab + * Adds Tier & Package Prices settings to WooCommerce Settings with sub-tabs * * @package WC_Tier_Package_Prices */ @@ -21,40 +21,39 @@ if (!class_exists('WC_Settings_Page')) { 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'); + /** + * Constructor + */ + public function __construct() { + $this->id = 'tier_package_prices'; + $this->label = __('Tier & Package Prices', 'wc-tier-package-prices'); - parent::__construct(); - } + parent::__construct(); - /** - * Get sections - * - * @return array - */ - public function get_sections() { - $sections = array( - '' => __('General', 'wc-tier-package-prices'), - ); + // 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')); + } - return apply_filters('woocommerce_get_sections_' . $this->id, $sections); - } + /** + * 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 array - * - * @param string $current_section Current section name. - * @return array - */ - public function get_settings($current_section = '') { - $settings = array(); - - if ('' === $current_section) { - $settings = array( + /** + * 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', @@ -121,23 +120,352 @@ if (!class_exists('WC_TPP_Settings')) { ); } - return apply_filters('woocommerce_get_settings_' . $this->id, $settings, $current_section); - } + /** + * 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', + ), - /** - * Output the settings - */ - public function output() { - $settings = $this->get_settings(); - WC_Admin_Settings::output_fields($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, + ), - /** - * Save settings - */ - public function save() { - $settings = $this->get_settings(); - WC_Admin_Settings::save_fields($settings); + 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' => __('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'] ?? '')); + + if (empty($license_key) || empty($server_url)) { + wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices'))); + } + + try { + $client = $this->get_license_client($server_url); + $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\LicenseException $e) { + delete_transient('wc_tpp_license_status'); + wp_send_json_error(array( + 'message' => $e->getMessage(), + 'code' => $e->errorCode ?? 'unknown', + )); + } catch (\Exception $e) { + delete_transient('wc_tpp_license_status'); + wp_send_json_error(array( + 'message' => $e->getMessage(), + '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'] ?? '')); + + if (empty($license_key) || empty($server_url)) { + wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices'))); + } + + try { + $client = $this->get_license_client($server_url); + $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\LicenseException $e) { + wp_send_json_error(array( + 'message' => $e->getMessage(), + 'code' => $e->errorCode ?? 'unknown', + )); + } catch (\Exception $e) { + wp_send_json_error(array( + 'message' => $e->getMessage(), + 'code' => 'exception', + )); + } + } + + /** + * Get license client instance + * + * @param string $server_url License server URL. + * @return \Magdev\WcLicensedProductClient\LicenseClientInterface + */ + private function get_license_client(string $server_url): \Magdev\WcLicensedProductClient\LicenseClientInterface { + $httpClient = \Symfony\Component\HttpClient\HttpClient::create(); + return new \Magdev\WcLicensedProductClient\LicenseClient( + httpClient: $httpClient, + baseUrl: $server_url, + ); + } + + /** + * 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(); + ?> +
+ + + +
+