You've already forked wc-licensed-product
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:
103
src/Product/LicensedProduct.php
Normal file
103
src/Product/LicensedProduct.php
Normal 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);
|
||||
}
|
||||
}
|
||||
200
src/Product/LicensedProductType.php
Normal file
200
src/Product/LicensedProductType.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user