diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c896a..5e287fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.6.1] - 2026-01-27 + +### Added + +- Filter functionality on customer account licenses page (filter by product or domain) +- Split auto-update settings into two options: "Enable Update Notifications" and "Automatically Install Updates" +- New `isUpdateNotificationEnabled()`, `isAutoInstallEnabled()` static methods in SettingsController +- WordPress auto-update filter integration (`auto_update_plugin`) for automatic installation + +### Fixed + +- Fixed admin license test popup showing empty product field +- `handleAjaxTestLicense()` now enriches response with product name +- Removed version field from test popup (version_id is only set for version-bound licenses) + +### Changed + +- Updated `magdev/wc-licensed-product-client` dependency to v0.2.1 +- "Automatically Install Updates" is only selectable when "Enable Update Notifications" is enabled + +## [0.6.0] - 2026-01-27 + +### Added + +- WordPress-style automatic update system for licensed plugins +- Server-side `/update-check` REST API endpoint for WordPress-compatible update information +- Client-side `PluginUpdateChecker` singleton for WordPress update integration +- New "Auto-Updates" settings subtab with enable/disable and check frequency options +- Secure download authentication via `X-License-Key` header +- Response signing support for tamper-proof update responses +- Configurable cache TTL for update checks (1-168 hours) + +### Changed + +- Updated OpenAPI specification to version 0.6.0 with `/update-check` endpoint documentation + ## [0.5.15] - 2026-01-27 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index cb059b4..15bca87 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w ### Version 0.7.0 -*No planned features yet.* +No changes planned at the moment ## Technical Stack @@ -1732,3 +1732,53 @@ define('WC_LICENSE_DISABLE_AUTO_UPDATE', true); - Created release package: `releases/wc-licensed-product-0.6.0.zip` (1.1 MB) - SHA256: `171c8195c586b3b20bac4a806e2d698cdaaf15966e2fd6e1670ec39dac8ab027` - Tagged as `v0.6.0` and pushed to `main` branch + +### 2026-01-27 - Version 0.6.1 - UI Improvements & Bug Fixes + +**Overview:** + +Bug fix and improvement release addressing admin license testing, auto-update settings, and customer license filtering. + +**Implemented:** + +- Filter functionality on customer account licenses page (filter by product or domain) +- Split auto-update settings into "Enable Update Notifications" and "Automatically Install Updates" +- WordPress `auto_update_plugin` filter integration for automatic installation + +**Bug Fixes:** + +- Fixed admin license test popup showing empty product field +- Removed version field from test popup (version_id is only set for version-bound licenses) +- `handleAjaxTestLicense()` now enriches response with product name + +**Modified files:** + +- `src/Admin/AdminController.php` - Enriched test license response with product name +- `src/Admin/SettingsController.php` - Split auto-update settings, added static helper methods +- `src/Update/PluginUpdateChecker.php` - Added `auto_update_plugin` filter, use new settings methods +- `src/Frontend/AccountController.php` - Added filter functionality with `applyLicenseFilters()` method +- `templates/frontend/licenses.html.twig` - Added filter form with product and domain dropdowns +- `templates/admin/licenses.html.twig` - Removed version row from test license modal +- `assets/css/frontend.css` - Added responsive styles for filter form +- `languages/*` - Updated all translation files + +**New methods in SettingsController:** + +- `isUpdateNotificationEnabled()` - Check if update notifications are enabled +- `isAutoInstallEnabled()` - Check if auto-install is enabled (requires notifications enabled) + +**New methods in AccountController:** + +- `applyLicenseFilters()` - Filter licenses by product ID and/or domain +- `getFilterOptions()` - Get unique products and domains for filter dropdowns + +**Technical notes:** + +- Filter form uses GET parameters: `filter_product` and `filter_domain` +- Auto-install setting is disabled (greyed out) when update notifications are disabled +- License test popup now only shows Product and Expires fields (version removed) +- Domain filter uses case-insensitive partial matching via `stripos()` + +**Dependency Updates:** + +- Updated `magdev/wc-licensed-product-client` from v0.2.0 to v0.2.1 diff --git a/assets/css/frontend.css b/assets/css/frontend.css index 4ec55ac..2a4c10d 100644 --- a/assets/css/frontend.css +++ b/assets/css/frontend.css @@ -37,6 +37,80 @@ color: #383d41; } +/* Filter Form */ +.wclp-filter-form { + margin-bottom: 1.5em; + padding: 1em; + background-color: #f8f9fa; + border: 1px solid #e5e5e5; + border-radius: 8px; +} + +.wclp-filter-row { + display: flex; + flex-wrap: wrap; + gap: 1em; + align-items: flex-end; +} + +.wclp-filter-field { + display: flex; + flex-direction: column; + gap: 0.3em; + flex: 1; + min-width: 150px; +} + +.wclp-filter-field label { + font-size: 0.85em; + font-weight: 600; + color: #666; +} + +.wclp-filter-field select { + width: 100%; + padding: 0.5em 0.75em; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #fff; + font-size: 0.95em; +} + +.wclp-filter-field select:focus { + border-color: #0073aa; + outline: none; + box-shadow: 0 0 0 1px #0073aa; +} + +.wclp-filter-actions { + display: flex; + gap: 0.5em; +} + +.wclp-filter-actions .button { + padding: 0.5em 1em; + font-size: 0.95em; + white-space: nowrap; +} + +@media (max-width: 600px) { + .wclp-filter-row { + flex-direction: column; + } + + .wclp-filter-field { + min-width: 100%; + } + + .wclp-filter-actions { + width: 100%; + } + + .wclp-filter-actions .button { + flex: 1; + } +} + /* License Packages */ .woocommerce-licenses { display: flex; diff --git a/composer.lock b/composer.lock index f5a9061..bc336a8 100644 --- a/composer.lock +++ b/composer.lock @@ -12,7 +12,7 @@ "source": { "type": "git", "url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git", - "reference": "5e4b5a970f75d0163c5496581d963a24ade4f276" + "reference": "760e1e752a0c088fa634cf7ff678e0735ed525a4" }, "require": { "php": "^8.3", @@ -52,7 +52,7 @@ "issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues", "source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client" }, - "time": "2026-01-26T15:54:37+00:00" + "time": "2026-01-27T19:52:12+00:00" }, { "name": "psr/cache", diff --git a/src/Admin/AdminController.php b/src/Admin/AdminController.php index 4903a2b..7bd6521 100644 --- a/src/Admin/AdminController.php +++ b/src/Admin/AdminController.php @@ -379,6 +379,19 @@ final class AdminController // Validate the license using LicenseManager $result = $this->licenseManager->validateLicense($licenseKey, $domain); + // Enrich result with product name for display in the popup + if (!empty($result['valid']) && isset($result['license'])) { + // Get product name + $productId = $result['license']['product_id'] ?? null; + if ($productId) { + $product = wc_get_product($productId); + $result['product_name'] = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product'); + } + + // Flatten expires_at for easier access in JavaScript + $result['expires_at'] = $result['license']['expires_at'] ?? null; + } + wp_send_json_success($result); } @@ -1605,12 +1618,11 @@ final class AdminController if (result.valid) { html = '
✓
| ' + escapeHtml(result.product_name || '-') + ' | |
| ' + escapeHtml(result.version || '-') + ' | |
| ' + escapeHtml(result.product_name || '-') + ' | |
| ' + escapeHtml(result.expires_at) + ' | |
' . esc_html__('No licenses found matching your filters.', 'wc-licensed-product') . '
'; + } else { + echo '' . esc_html__('You have no licenses yet.', 'wc-licensed-product') . '
'; + } return; } diff --git a/src/Update/PluginUpdateChecker.php b/src/Update/PluginUpdateChecker.php index fb7974c..2062897 100644 --- a/src/Update/PluginUpdateChecker.php +++ b/src/Update/PluginUpdateChecker.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Jeremias\WcLicensedProduct\Update; +use Jeremias\WcLicensedProduct\Admin\SettingsController; use Jeremias\WcLicensedProduct\License\PluginLicenseChecker; use Symfony\Component\HttpClient\HttpClient; @@ -74,8 +75,8 @@ final class PluginUpdateChecker */ public function register(): void { - // Skip if auto-updates are disabled - if ($this->isAutoUpdateDisabled()) { + // Skip if update notifications are disabled + if ($this->isUpdateNotificationDisabled()) { return; } @@ -88,15 +89,19 @@ final class PluginUpdateChecker // Add authentication headers to download requests add_filter('http_request_args', [$this, 'addAuthHeaders'], 10, 2); + // Handle auto-install setting + add_filter('auto_update_plugin', [$this, 'handleAutoInstall'], 10, 2); + // Clear cache on settings save add_action('update_option_wc_licensed_product_plugin_license_key', [$this, 'clearCache']); add_action('update_option_wc_licensed_product_plugin_license_server_url', [$this, 'clearCache']); + add_action('update_option_wc_licensed_product_update_notification_enabled', [$this, 'clearCache']); } /** - * Check if auto-updates are disabled + * Check if update notifications are disabled */ - private function isAutoUpdateDisabled(): bool + private function isUpdateNotificationDisabled(): bool { // Check constant if (defined('WC_LICENSE_DISABLE_AUTO_UPDATE') && WC_LICENSE_DISABLE_AUTO_UPDATE) { @@ -104,8 +109,25 @@ final class PluginUpdateChecker } // Check setting - $enabled = get_option('wc_licensed_product_plugin_auto_update_enabled', 'yes'); - return $enabled !== 'yes'; + return !SettingsController::isUpdateNotificationEnabled(); + } + + /** + * Handle auto-install setting for WordPress automatic updates + * + * @param bool|null $update The update decision + * @param object $item The plugin update object + * @return bool|null Whether to auto-update this plugin + */ + public function handleAutoInstall($update, $item): ?bool + { + // Only handle our plugin + if (!isset($item->plugin) || $item->plugin !== $this->pluginBasename) { + return $update; + } + + // Return true to enable auto-install, false to disable, or null to use default + return SettingsController::isAutoInstallEnabled() ? true : $update; } /** diff --git a/templates/admin/licenses.html.twig b/templates/admin/licenses.html.twig index 38af54f..d121fa7 100644 --- a/templates/admin/licenses.html.twig +++ b/templates/admin/licenses.html.twig @@ -424,12 +424,11 @@ if (result.valid) { html = '✓ {{ __('License is VALID') }}
| {{ __('Product') }} | ' + escapeHtml(result.product_name || '-') + ' |
|---|---|
| {{ __('Version') }} | ' + escapeHtml(result.version || '-') + ' |
| {{ __('Product') }} | ' + escapeHtml(result.product_name || '-') + ' |
| {{ __('Expires') }} | ' + escapeHtml(result.expires_at) + ' |
| {{ __('Expires') }} | {{ __('Lifetime') }} |
| {{ __('Expires') }} | {{ __('Lifetime') }} |
{{ __('You have no licenses yet.') }}
+ {% if is_filtered %} +{{ __('No licenses found matching your filters.') }}
+ {% else %} +{{ __('You have no licenses yet.') }}
+ {% endif %} {% else %}