Implement version 0.0.1 - Licensed Product type for WooCommerce

Add complete plugin infrastructure for selling software with license keys:

- New "Licensed Product" WooCommerce product type
- License key generation (XXXX-XXXX-XXXX-XXXX format) on order completion
- Domain-based license validation system
- REST API endpoints (validate, status, activate, deactivate)
- Customer My Account "Licenses" page
- Admin license management under WooCommerce > Licenses
- Checkout domain field for licensed products
- Custom database tables for licenses and product versions
- Twig template engine integration
- Full i18n support with German (de_CH) translation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 18:55:18 +01:00
parent 8a4802248c
commit 404083f023
22 changed files with 3746 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
<?php
/**
* Licensed Product Class
*
* @package Jeremias\WcLicensedProduct\Product
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Product;
use WC_Product;
/**
* Licensed Product type extending WooCommerce Product
*/
class LicensedProduct extends WC_Product
{
/**
* Product type
*/
protected $product_type = 'licensed';
/**
* Constructor
*/
public function __construct($product = 0)
{
parent::__construct($product);
}
/**
* Get product type
*/
public function get_type(): string
{
return 'licensed';
}
/**
* Licensed products are always virtual
*/
public function is_virtual(): bool
{
return true;
}
/**
* Licensed products are purchasable
*/
public function is_purchasable(): bool
{
return $this->exists() && $this->get_price() !== '';
}
/**
* Get max activations for this product
*/
public function get_max_activations(): int
{
$value = $this->get_meta('_licensed_max_activations', true);
return $value ? (int) $value : 1;
}
/**
* Get validity days
*/
public function get_validity_days(): ?int
{
$value = $this->get_meta('_licensed_validity_days', true);
return $value !== '' ? (int) $value : null;
}
/**
* Check if license should be bound to major version
*/
public function is_bound_to_version(): bool
{
return $this->get_meta('_licensed_bind_to_version', true) === 'yes';
}
/**
* Get current software version
*/
public function get_current_version(): string
{
return $this->get_meta('_licensed_current_version', true) ?: '';
}
/**
* Get major version number from version string
*/
public function get_major_version(): int
{
$version = $this->get_current_version();
if (empty($version)) {
return 1;
}
$parts = explode('.', $version);
return (int) ($parts[0] ?? 1);
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* Licensed Product Type
*
* @package Jeremias\WcLicensedProduct\Product
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Product;
/**
* Registers and handles the Licensed product type for WooCommerce
*/
final class LicensedProductType
{
/**
* Constructor
*/
public function __construct()
{
$this->registerHooks();
}
/**
* Register WordPress hooks
*/
private function registerHooks(): void
{
// Register product type
add_filter('product_type_selector', [$this, 'addProductType']);
add_filter('woocommerce_product_class', [$this, 'getProductClass'], 10, 2);
// Add product data tabs
add_filter('woocommerce_product_data_tabs', [$this, 'addProductDataTab']);
add_action('woocommerce_product_data_panels', [$this, 'addProductDataPanel']);
// Save product meta
add_action('woocommerce_process_product_meta_licensed', [$this, 'saveProductMeta']);
// Show price and add to cart for licensed products
add_action('woocommerce_licensed_add_to_cart', [$this, 'addToCartTemplate']);
// Make product virtual by default
add_filter('woocommerce_product_is_virtual', [$this, 'isVirtual'], 10, 2);
}
/**
* Add product type to selector
*/
public function addProductType(array $types): array
{
$types['licensed'] = __('Licensed Product', 'wc-licensed-product');
return $types;
}
/**
* Get product class for licensed type
*/
public function getProductClass(string $className, string $productType): string
{
if ($productType === 'licensed') {
return LicensedProduct::class;
}
return $className;
}
/**
* Add product data tab for license settings
*/
public function addProductDataTab(array $tabs): array
{
$tabs['licensed_product'] = [
'label' => __('License Settings', 'wc-licensed-product'),
'target' => 'licensed_product_data',
'class' => ['show_if_licensed'],
'priority' => 21,
];
return $tabs;
}
/**
* Add product data panel content
*/
public function addProductDataPanel(): void
{
global $post;
?>
<div id="licensed_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?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'),
'desc_tip' => true,
'type' => 'number',
'custom_attributes' => [
'min' => '1',
'step' => '1',
],
'value' => get_post_meta($post->ID, '_licensed_max_activations', true) ?: '1',
]);
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'),
'desc_tip' => true,
'type' => 'number',
'custom_attributes' => [
'min' => '0',
'step' => '1',
],
]);
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'),
]);
woocommerce_wp_text_input([
'id' => '_licensed_current_version',
'label' => __('Current Version', 'wc-licensed-product'),
'description' => __('Current software version (e.g., 1.0.0)', 'wc-licensed-product'),
'desc_tip' => true,
'placeholder' => '1.0.0',
]);
?>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Show/hide panels based on product type
$('select#product-type').change(function() {
if ($(this).val() === 'licensed') {
$('.show_if_licensed').show();
$('.general_options').show();
$('.pricing').show();
} else {
$('.show_if_licensed').hide();
}
}).change();
// Show general tab for licensed products
$('#product-type').on('change', function() {
if ($(this).val() === 'licensed') {
$('.general_tab').show();
}
});
});
</script>
<?php
}
/**
* Save product meta
*/
public function saveProductMeta(int $postId): void
{
// Verify nonce is handled by WooCommerce
$maxActivations = isset($_POST['_licensed_max_activations'])
? absint($_POST['_licensed_max_activations'])
: 1;
update_post_meta($postId, '_licensed_max_activations', $maxActivations);
$validityDays = isset($_POST['_licensed_validity_days']) && $_POST['_licensed_validity_days'] !== ''
? absint($_POST['_licensed_validity_days'])
: '';
update_post_meta($postId, '_licensed_validity_days', $validityDays);
$bindToVersion = isset($_POST['_licensed_bind_to_version']) ? 'yes' : 'no';
update_post_meta($postId, '_licensed_bind_to_version', $bindToVersion);
$currentVersion = isset($_POST['_licensed_current_version'])
? sanitize_text_field($_POST['_licensed_current_version'])
: '';
update_post_meta($postId, '_licensed_current_version', $currentVersion);
}
/**
* Add to cart template for licensed products
*/
public function addToCartTemplate(): void
{
wc_get_template('single-product/add-to-cart/simple.php');
}
/**
* Make licensed products virtual by default
*/
public function isVirtual(bool $isVirtual, \WC_Product $product): bool
{
if ($product->is_type('licensed')) {
return true;
}
return $isVirtual;
}
}