You've already forked wc-licensed-product
Implement versions 0.0.4-0.0.7 features
v0.0.4: - Add WooCommerce settings tab for default license settings - Per-product settings override global defaults v0.0.5: - Add bulk license operations (activate, deactivate, revoke, extend, delete) - Add license renewal/extension and lifetime functionality - Add quick action buttons per license row v0.0.6: - Add license dashboard with statistics and analytics - Add license transfer functionality (admin) - Add CSV export for licenses - Add OpenAPI 3.1 specification - Remove /deactivate API endpoint v0.0.7: - Move license dashboard to WooCommerce Reports section - Add license search and filtering in admin - Add customer-facing license transfer with AJAX modal - Add email notifications for license expiration warnings - Add bulk import licenses from CSV - Update README with comprehensive documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Jeremias\WcLicensedProduct\Product;
|
||||
|
||||
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
||||
use WC_Product;
|
||||
|
||||
/**
|
||||
@@ -55,28 +56,68 @@ class LicensedProduct extends WC_Product
|
||||
|
||||
/**
|
||||
* Get max activations for this product
|
||||
* Falls back to default settings if not set on product
|
||||
*/
|
||||
public function get_max_activations(): int
|
||||
{
|
||||
$value = $this->get_meta('_licensed_max_activations', true);
|
||||
return $value ? (int) $value : 1;
|
||||
if ($value !== '' && $value !== null) {
|
||||
return max(1, (int) $value);
|
||||
}
|
||||
return SettingsController::getDefaultMaxActivations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product has custom max activations set
|
||||
*/
|
||||
public function has_custom_max_activations(): bool
|
||||
{
|
||||
$value = $this->get_meta('_licensed_max_activations', true);
|
||||
return $value !== '' && $value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validity days
|
||||
* Falls back to default settings if not set on product
|
||||
*/
|
||||
public function get_validity_days(): ?int
|
||||
{
|
||||
$value = $this->get_meta('_licensed_validity_days', true);
|
||||
return $value !== '' ? (int) $value : null;
|
||||
if ($value !== '' && $value !== null) {
|
||||
return (int) $value > 0 ? (int) $value : null;
|
||||
}
|
||||
return SettingsController::getDefaultValidityDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product has custom validity days set
|
||||
*/
|
||||
public function has_custom_validity_days(): bool
|
||||
{
|
||||
$value = $this->get_meta('_licensed_validity_days', true);
|
||||
return $value !== '' && $value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if license should be bound to major version
|
||||
* Falls back to default settings if not set on product
|
||||
*/
|
||||
public function is_bound_to_version(): bool
|
||||
{
|
||||
return $this->get_meta('_licensed_bind_to_version', true) === 'yes';
|
||||
$value = $this->get_meta('_licensed_bind_to_version', true);
|
||||
if ($value !== '' && $value !== null) {
|
||||
return $value === 'yes';
|
||||
}
|
||||
return SettingsController::getDefaultBindToVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product has custom bind to version setting
|
||||
*/
|
||||
public function has_custom_bind_to_version(): bool
|
||||
{
|
||||
$value = $this->get_meta('_licensed_bind_to_version', true);
|
||||
return $value !== '' && $value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Jeremias\WcLicensedProduct\Product;
|
||||
|
||||
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
||||
|
||||
/**
|
||||
* Registers and handles the Licensed product type for WooCommerce
|
||||
*/
|
||||
@@ -85,39 +87,82 @@ final class LicensedProductType
|
||||
public function addProductDataPanel(): void
|
||||
{
|
||||
global $post;
|
||||
|
||||
// Get current product values
|
||||
$currentMaxActivations = get_post_meta($post->ID, '_licensed_max_activations', true);
|
||||
$currentValidityDays = get_post_meta($post->ID, '_licensed_validity_days', true);
|
||||
$currentBindToVersion = get_post_meta($post->ID, '_licensed_bind_to_version', true);
|
||||
|
||||
// Get default values
|
||||
$defaultMaxActivations = SettingsController::getDefaultMaxActivations();
|
||||
$defaultValidityDays = SettingsController::getDefaultValidityDays();
|
||||
$defaultBindToVersion = SettingsController::getDefaultBindToVersion();
|
||||
|
||||
// Format default validity for display
|
||||
$defaultValidityDisplay = $defaultValidityDays !== null
|
||||
? sprintf(__('%d days', 'wc-licensed-product'), $defaultValidityDays)
|
||||
: __('Lifetime', 'wc-licensed-product');
|
||||
|
||||
?>
|
||||
<div id="licensed_product_data" class="panel woocommerce_options_panel hidden">
|
||||
<div class="options_group">
|
||||
<p class="form-field">
|
||||
<em><?php
|
||||
printf(
|
||||
/* translators: %s: URL to settings page */
|
||||
esc_html__('Leave fields empty to use default settings from %s.', 'wc-licensed-product'),
|
||||
'<a href="' . esc_url(admin_url('admin.php?page=wc-settings&tab=licensed_product')) . '">' .
|
||||
esc_html__('WooCommerce > Settings > Licensed Products', 'wc-licensed-product') . '</a>'
|
||||
);
|
||||
?></em>
|
||||
</p>
|
||||
<?php
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_licensed_max_activations',
|
||||
'label' => __('Max Activations', 'wc-licensed-product'),
|
||||
'description' => __('Maximum number of domain activations per license. Default: 1', 'wc-licensed-product'),
|
||||
'description' => sprintf(
|
||||
/* translators: %d: default max activations value */
|
||||
__('Maximum number of domain activations per license. Default: %d', 'wc-licensed-product'),
|
||||
$defaultMaxActivations
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'type' => 'number',
|
||||
'custom_attributes' => [
|
||||
'min' => '1',
|
||||
'step' => '1',
|
||||
],
|
||||
'value' => get_post_meta($post->ID, '_licensed_max_activations', true) ?: '1',
|
||||
'placeholder' => (string) $defaultMaxActivations,
|
||||
'value' => $currentMaxActivations,
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_licensed_validity_days',
|
||||
'label' => __('License Validity (Days)', 'wc-licensed-product'),
|
||||
'description' => __('Number of days the license is valid. Leave empty for lifetime license.', 'wc-licensed-product'),
|
||||
'description' => sprintf(
|
||||
/* translators: %s: default validity value */
|
||||
__('Number of days the license is valid. Leave empty for default (%s).', 'wc-licensed-product'),
|
||||
$defaultValidityDisplay
|
||||
),
|
||||
'desc_tip' => true,
|
||||
'type' => 'number',
|
||||
'custom_attributes' => [
|
||||
'min' => '0',
|
||||
'step' => '1',
|
||||
],
|
||||
'placeholder' => $defaultValidityDays !== null ? (string) $defaultValidityDays : __('Lifetime', 'wc-licensed-product'),
|
||||
'value' => $currentValidityDays,
|
||||
]);
|
||||
|
||||
woocommerce_wp_checkbox([
|
||||
'id' => '_licensed_bind_to_version',
|
||||
'label' => __('Bind to Major Version', 'wc-licensed-product'),
|
||||
'description' => __('If enabled, licenses are bound to the major version at purchase time.', 'wc-licensed-product'),
|
||||
'description' => sprintf(
|
||||
/* translators: %s: default bind to version value (Yes/No) */
|
||||
__('If enabled, licenses are bound to the major version at purchase time. Default: %s', 'wc-licensed-product'),
|
||||
$defaultBindToVersion ? __('Yes', 'wc-licensed-product') : __('No', 'wc-licensed-product')
|
||||
),
|
||||
'value' => $currentBindToVersion ?: ($defaultBindToVersion ? 'yes' : 'no'),
|
||||
'cbvalue' => 'yes',
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
@@ -160,19 +205,26 @@ final class LicensedProductType
|
||||
public function saveProductMeta(int $postId): void
|
||||
{
|
||||
// Verify nonce is handled by WooCommerce
|
||||
$maxActivations = isset($_POST['_licensed_max_activations'])
|
||||
// Allow empty values to fall back to defaults
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by WooCommerce
|
||||
$maxActivations = isset($_POST['_licensed_max_activations']) && $_POST['_licensed_max_activations'] !== ''
|
||||
? absint($_POST['_licensed_max_activations'])
|
||||
: 1;
|
||||
: '';
|
||||
update_post_meta($postId, '_licensed_max_activations', $maxActivations);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$validityDays = isset($_POST['_licensed_validity_days']) && $_POST['_licensed_validity_days'] !== ''
|
||||
? absint($_POST['_licensed_validity_days'])
|
||||
: '';
|
||||
update_post_meta($postId, '_licensed_validity_days', $validityDays);
|
||||
|
||||
// For checkbox, we need to distinguish between "not set" and "explicitly unchecked"
|
||||
// If the hidden field is present, the form was submitted and we save the actual value
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$bindToVersion = isset($_POST['_licensed_bind_to_version']) ? 'yes' : 'no';
|
||||
update_post_meta($postId, '_licensed_bind_to_version', $bindToVersion);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$currentVersion = isset($_POST['_licensed_current_version'])
|
||||
? sanitize_text_field($_POST['_licensed_current_version'])
|
||||
: '';
|
||||
|
||||
Reference in New Issue
Block a user