You've already forked wc-licensed-product
Release v0.6.1 - UI improvements and bug fixes
- Fix admin license test popup showing empty product field - Display product name in bold in test license modal - Split auto-update settings into notification and auto-install options - Add filter functionality to customer account licenses page - Update translations (402 strings) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 = '<div class="notice notice-success inline"><p><strong>✓ <?php echo esc_js(__('License is VALID', 'wc-licensed-product')); ?></strong></p></div>';
|
||||
html += '<table class="widefat striped"><tbody>';
|
||||
html += '<tr><th><?php echo esc_js(__('Product', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.product_name || '-') + '</td></tr>';
|
||||
html += '<tr><th><?php echo esc_js(__('Version', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.version || '-') + '</td></tr>';
|
||||
html += '<tr><th><?php echo esc_js(__('Product', 'wc-licensed-product')); ?></th><td><strong>' + escapeHtml(result.product_name || '-') + '</strong></td></tr>';
|
||||
if (result.expires_at) {
|
||||
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td>' + escapeHtml(result.expires_at) + '</td></tr>';
|
||||
} else {
|
||||
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td><?php echo esc_js(__('Lifetime', 'wc-licensed-product')); ?></td></tr>';
|
||||
html += '<tr><th><?php echo esc_js(__('Expires', 'wc-licensed-product')); ?></th><td><span class="license-lifetime"><?php echo esc_js(__('Lifetime', 'wc-licensed-product')); ?></span></td></tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
} else {
|
||||
|
||||
@@ -167,6 +167,8 @@ final class SettingsController
|
||||
*/
|
||||
private function getAutoUpdatesSettings(): array
|
||||
{
|
||||
$autoInstallDisabled = !self::isUpdateNotificationEnabled();
|
||||
|
||||
return [
|
||||
'auto_update_section_title' => [
|
||||
'name' => __('Auto-Updates', 'wc-licensed-product'),
|
||||
@@ -174,13 +176,23 @@ final class SettingsController
|
||||
'desc' => __('Configure automatic plugin updates from the license server.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_section_auto_update',
|
||||
],
|
||||
'plugin_auto_update_enabled' => [
|
||||
'name' => __('Enable Auto-Updates', 'wc-licensed-product'),
|
||||
'update_notification_enabled' => [
|
||||
'name' => __('Enable Update Notifications', 'wc-licensed-product'),
|
||||
'type' => 'checkbox',
|
||||
'desc' => __('Automatically check for and receive plugin updates from the license server.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_plugin_auto_update_enabled',
|
||||
'desc' => __('Check for and display available updates from the license server in WordPress admin.', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_update_notification_enabled',
|
||||
'default' => 'yes',
|
||||
],
|
||||
'plugin_auto_install_enabled' => [
|
||||
'name' => __('Automatically Install Updates', 'wc-licensed-product'),
|
||||
'type' => 'checkbox',
|
||||
'desc' => $autoInstallDisabled
|
||||
? __('Enable "Update Notifications" above to use this option.', 'wc-licensed-product')
|
||||
: __('Automatically install updates when they become available (requires update notifications enabled).', 'wc-licensed-product'),
|
||||
'id' => 'wc_licensed_product_plugin_auto_install_enabled',
|
||||
'default' => 'no',
|
||||
'custom_attributes' => $autoInstallDisabled ? ['disabled' => 'disabled'] : [],
|
||||
],
|
||||
'update_check_frequency' => [
|
||||
'name' => __('Check Frequency (Hours)', 'wc-licensed-product'),
|
||||
'type' => 'number',
|
||||
@@ -501,11 +513,32 @@ final class SettingsController
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if auto-updates are enabled
|
||||
* Check if update notifications are enabled
|
||||
*/
|
||||
public static function isUpdateNotificationEnabled(): bool
|
||||
{
|
||||
return get_option('wc_licensed_product_update_notification_enabled', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if auto-updates are enabled (legacy alias for isUpdateNotificationEnabled)
|
||||
*/
|
||||
public static function isAutoUpdateEnabled(): bool
|
||||
{
|
||||
return get_option('wc_licensed_product_plugin_auto_update_enabled', 'yes') === 'yes';
|
||||
return self::isUpdateNotificationEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if automatic installation of updates is enabled
|
||||
*/
|
||||
public static function isAutoInstallEnabled(): bool
|
||||
{
|
||||
// Auto-install requires notifications to be enabled first
|
||||
if (!self::isUpdateNotificationEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_option('wc_licensed_product_plugin_auto_install_enabled', 'no') === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,23 +106,104 @@ final class AccountController
|
||||
return;
|
||||
}
|
||||
|
||||
// Get filter parameters from URL
|
||||
$filterProductId = isset($_GET['filter_product']) ? absint($_GET['filter_product']) : 0;
|
||||
$filterDomain = isset($_GET['filter_domain']) ? sanitize_text_field(wp_unslash($_GET['filter_domain'])) : '';
|
||||
|
||||
$licenses = $this->licenseManager->getLicensesByCustomer($customerId);
|
||||
|
||||
// Apply filters
|
||||
$filteredLicenses = $this->applyLicenseFilters($licenses, $filterProductId, $filterDomain);
|
||||
|
||||
// Group licenses by product+order into "packages"
|
||||
$packages = $this->groupLicensesIntoPackages($licenses);
|
||||
$packages = $this->groupLicensesIntoPackages($filteredLicenses);
|
||||
|
||||
// Get unique products and domains for filter dropdowns
|
||||
$filterOptions = $this->getFilterOptions($licenses);
|
||||
|
||||
try {
|
||||
echo $this->twig->render('frontend/licenses.html.twig', [
|
||||
'packages' => $packages,
|
||||
'has_packages' => !empty($packages),
|
||||
'signing_enabled' => ResponseSigner::isSigningEnabled(),
|
||||
'filter_products' => $filterOptions['products'],
|
||||
'filter_domains' => $filterOptions['domains'],
|
||||
'current_filter_product' => $filterProductId,
|
||||
'current_filter_domain' => $filterDomain,
|
||||
'is_filtered' => $filterProductId > 0 || !empty($filterDomain),
|
||||
'licenses_url' => wc_get_account_endpoint_url('licenses'),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// Fallback to PHP template if Twig fails
|
||||
$this->displayLicensesFallback($packages);
|
||||
$this->displayLicensesFallback($packages, $filterOptions, $filterProductId, $filterDomain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to licenses
|
||||
*
|
||||
* @param array $licenses Array of License objects
|
||||
* @param int $productId Filter by product ID (0 for all)
|
||||
* @param string $domain Filter by domain (empty for all)
|
||||
* @return array Filtered array of License objects
|
||||
*/
|
||||
private function applyLicenseFilters(array $licenses, int $productId, string $domain): array
|
||||
{
|
||||
if ($productId === 0 && empty($domain)) {
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
return array_filter($licenses, function ($license) use ($productId, $domain) {
|
||||
// Filter by product
|
||||
if ($productId > 0 && $license->getProductId() !== $productId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter by domain (partial match)
|
||||
if (!empty($domain) && stripos($license->getDomain(), $domain) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique filter options from licenses
|
||||
*
|
||||
* @param array $licenses Array of License objects
|
||||
* @return array Array with 'products' and 'domains' keys
|
||||
*/
|
||||
private function getFilterOptions(array $licenses): array
|
||||
{
|
||||
$products = [];
|
||||
$domains = [];
|
||||
|
||||
foreach ($licenses as $license) {
|
||||
// Collect unique products
|
||||
$productId = $license->getProductId();
|
||||
if (!isset($products[$productId])) {
|
||||
$product = wc_get_product($productId);
|
||||
$products[$productId] = $product ? $product->get_name() : __('Unknown Product', 'wc-licensed-product');
|
||||
}
|
||||
|
||||
// Collect unique domains
|
||||
$domain = $license->getDomain();
|
||||
if (!in_array($domain, $domains, true)) {
|
||||
$domains[] = $domain;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort products by name, domains alphabetically
|
||||
asort($products);
|
||||
sort($domains);
|
||||
|
||||
return [
|
||||
'products' => $products,
|
||||
'domains' => $domains,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Group licenses into packages by product+order
|
||||
*
|
||||
@@ -217,10 +298,67 @@ final class AccountController
|
||||
/**
|
||||
* Fallback display method if Twig is unavailable
|
||||
*/
|
||||
private function displayLicensesFallback(array $packages): void
|
||||
{
|
||||
private function displayLicensesFallback(
|
||||
array $packages,
|
||||
array $filterOptions = [],
|
||||
int $currentFilterProduct = 0,
|
||||
string $currentFilterDomain = ''
|
||||
): void {
|
||||
$isFiltered = $currentFilterProduct > 0 || !empty($currentFilterDomain);
|
||||
$licensesUrl = wc_get_account_endpoint_url('licenses');
|
||||
|
||||
// Display filter form if we have filter options
|
||||
if (!empty($filterOptions['products']) || !empty($filterOptions['domains'])) {
|
||||
?>
|
||||
<div class="wclp-filter-form">
|
||||
<form method="get" action="<?php echo esc_url($licensesUrl); ?>">
|
||||
<div class="wclp-filter-row">
|
||||
<?php if (!empty($filterOptions['products'])): ?>
|
||||
<div class="wclp-filter-field">
|
||||
<label for="filter_product"><?php esc_html_e('Product', 'wc-licensed-product'); ?></label>
|
||||
<select name="filter_product" id="filter_product">
|
||||
<option value=""><?php esc_html_e('All Products', 'wc-licensed-product'); ?></option>
|
||||
<?php foreach ($filterOptions['products'] as $id => $name): ?>
|
||||
<option value="<?php echo esc_attr($id); ?>" <?php selected($currentFilterProduct, $id); ?>>
|
||||
<?php echo esc_html($name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($filterOptions['domains'])): ?>
|
||||
<div class="wclp-filter-field">
|
||||
<label for="filter_domain"><?php esc_html_e('Domain', 'wc-licensed-product'); ?></label>
|
||||
<select name="filter_domain" id="filter_domain">
|
||||
<option value=""><?php esc_html_e('All Domains', 'wc-licensed-product'); ?></option>
|
||||
<?php foreach ($filterOptions['domains'] as $domain): ?>
|
||||
<option value="<?php echo esc_attr($domain); ?>" <?php selected($currentFilterDomain, $domain); ?>>
|
||||
<?php echo esc_html($domain); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="wclp-filter-actions">
|
||||
<button type="submit" class="button"><?php esc_html_e('Filter', 'wc-licensed-product'); ?></button>
|
||||
<?php if ($isFiltered): ?>
|
||||
<a href="<?php echo esc_url($licensesUrl); ?>" class="button"><?php esc_html_e('Clear', 'wc-licensed-product'); ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if (empty($packages)) {
|
||||
echo '<p>' . esc_html__('You have no licenses yet.', 'wc-licensed-product') . '</p>';
|
||||
if ($isFiltered) {
|
||||
echo '<p>' . esc_html__('No licenses found matching your filters.', 'wc-licensed-product') . '</p>';
|
||||
} else {
|
||||
echo '<p>' . esc_html__('You have no licenses yet.', 'wc-licensed-product') . '</p>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user