2026-01-26 16:14:15 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Licensed Product Variation Class
|
|
|
|
|
*
|
|
|
|
|
* @package Jeremias\WcLicensedProduct\Product
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Jeremias\WcLicensedProduct\Product;
|
|
|
|
|
|
|
|
|
|
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
|
|
|
|
use WC_Product_Variation;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Licensed Product Variation type extending WooCommerce Product Variation
|
|
|
|
|
*
|
|
|
|
|
* Each variation can have its own license duration settings.
|
|
|
|
|
*/
|
|
|
|
|
class LicensedProductVariation extends WC_Product_Variation
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
*/
|
|
|
|
|
public function __construct($product = 0)
|
|
|
|
|
{
|
|
|
|
|
parent::__construct($product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Licensed products are always virtual
|
|
|
|
|
*/
|
|
|
|
|
public function is_virtual(): bool
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 14:44:57 +01:00
|
|
|
/**
|
|
|
|
|
* Licensed products are always in stock (virtual, no inventory)
|
|
|
|
|
*/
|
|
|
|
|
public function is_in_stock(): bool
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get availability - empty for licensed products (no stock indicator)
|
|
|
|
|
*/
|
|
|
|
|
public function get_availability(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'availability' => '',
|
|
|
|
|
'class' => '',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Don't manage stock for licensed products
|
|
|
|
|
*/
|
|
|
|
|
public function managing_stock(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if variation is purchasable
|
|
|
|
|
* Override to handle custom parent product type
|
|
|
|
|
*/
|
|
|
|
|
public function is_purchasable(): bool
|
|
|
|
|
{
|
|
|
|
|
// Check if variation exists
|
|
|
|
|
if (!$this->exists()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check parent product status
|
|
|
|
|
$parentId = $this->get_parent_id();
|
|
|
|
|
$parentStatus = get_post_status($parentId);
|
|
|
|
|
|
|
|
|
|
if ($parentStatus !== 'publish' && !current_user_can('edit_post', $parentId)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if variation has a price
|
|
|
|
|
$price = $this->get_price();
|
|
|
|
|
if ($price === '' || $price === null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return apply_filters('woocommerce_variation_is_purchasable', true, $this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 16:14:15 +01:00
|
|
|
/**
|
|
|
|
|
* Get max activations for this variation
|
|
|
|
|
* Falls back to parent product, then to default settings
|
|
|
|
|
*/
|
|
|
|
|
public function get_max_activations(): int
|
|
|
|
|
{
|
|
|
|
|
// Check variation-specific setting first
|
|
|
|
|
$value = $this->get_meta('_licensed_max_activations', true);
|
|
|
|
|
if ($value !== '' && $value !== null) {
|
|
|
|
|
return max(1, (int) $value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall back to parent product
|
|
|
|
|
$parent = wc_get_product($this->get_parent_id());
|
|
|
|
|
if ($parent && method_exists($parent, 'get_max_activations')) {
|
|
|
|
|
return $parent->get_max_activations();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SettingsController::getDefaultMaxActivations();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if variation 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 for this variation
|
|
|
|
|
* This is the primary license setting that varies per variation
|
|
|
|
|
* Falls back to parent product, then to default settings
|
|
|
|
|
*/
|
|
|
|
|
public function get_validity_days(): ?int
|
|
|
|
|
{
|
|
|
|
|
// Check variation-specific setting first
|
|
|
|
|
$value = $this->get_meta('_licensed_validity_days', true);
|
|
|
|
|
if ($value !== '' && $value !== null) {
|
|
|
|
|
$days = (int) $value;
|
|
|
|
|
// 0 means lifetime
|
|
|
|
|
return $days > 0 ? $days : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall back to parent product
|
|
|
|
|
$parent = wc_get_product($this->get_parent_id());
|
|
|
|
|
if ($parent && method_exists($parent, 'get_validity_days')) {
|
|
|
|
|
return $parent->get_validity_days();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SettingsController::getDefaultValidityDays();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if variation 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 parent product, then to default settings
|
|
|
|
|
*/
|
|
|
|
|
public function is_bound_to_version(): bool
|
|
|
|
|
{
|
|
|
|
|
// Check variation-specific setting first
|
|
|
|
|
$value = $this->get_meta('_licensed_bind_to_version', true);
|
|
|
|
|
if ($value !== '' && $value !== null) {
|
|
|
|
|
return $value === 'yes';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall back to parent product
|
|
|
|
|
$parent = wc_get_product($this->get_parent_id());
|
|
|
|
|
if ($parent && method_exists($parent, 'is_bound_to_version')) {
|
|
|
|
|
return $parent->is_bound_to_version();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SettingsController::getDefaultBindToVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if variation 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the license duration label for display
|
|
|
|
|
*/
|
|
|
|
|
public function get_license_duration_label(): string
|
|
|
|
|
{
|
|
|
|
|
$days = $this->get_validity_days();
|
|
|
|
|
|
|
|
|
|
if ($days === null) {
|
|
|
|
|
return __('Lifetime', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($days === 30) {
|
|
|
|
|
return __('Monthly', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($days === 90) {
|
|
|
|
|
return __('Quarterly', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($days === 365) {
|
|
|
|
|
return __('Yearly', 'wc-licensed-product');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sprintf(
|
|
|
|
|
/* translators: %d: number of days */
|
|
|
|
|
_n('%d day', '%d days', $days, 'wc-licensed-product'),
|
|
|
|
|
$days
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current software version from parent product
|
|
|
|
|
*/
|
|
|
|
|
public function get_current_version(): string
|
|
|
|
|
{
|
|
|
|
|
$parent = wc_get_product($this->get_parent_id());
|
|
|
|
|
if ($parent && method_exists($parent, 'get_current_version')) {
|
|
|
|
|
return $parent->get_current_version();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$versionManager = new VersionManager();
|
|
|
|
|
$latestVersion = $versionManager->getLatestVersion($this->get_parent_id());
|
|
|
|
|
|
|
|
|
|
return $latestVersion ? $latestVersion->getVersion() : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get major version number from parent product
|
|
|
|
|
*/
|
|
|
|
|
public function get_major_version(): int
|
|
|
|
|
{
|
|
|
|
|
$parent = wc_get_product($this->get_parent_id());
|
|
|
|
|
if ($parent && method_exists($parent, 'get_major_version')) {
|
|
|
|
|
return $parent->get_major_version();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$versionManager = new VersionManager();
|
|
|
|
|
$latestVersion = $versionManager->getLatestVersion($this->get_parent_id());
|
|
|
|
|
|
|
|
|
|
if ($latestVersion) {
|
|
|
|
|
return $latestVersion->getMajorVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|