Fix stock indicator on licensed variable products (v0.5.12)

- Fixed stock indicator appearing in cart for licensed variable products
- Override get_children() with direct SQL query to bypass WooCommerce type check
- Override get_variation_attributes() for proper taxonomy attribute loading
- Override get_variation_prices() to prevent null array errors
- Override get_available_variations() with empty availability_html
- Added is_type() override to pass variable type checks
- Added multiple stock-related filters for comprehensive coverage
- Improved isLicensedProductOrVariation() with DB-level parent type check

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 14:44:57 +01:00
parent 20fb39d1a1
commit 142500cab0
11 changed files with 534 additions and 81 deletions

View File

@@ -41,6 +41,19 @@ class LicensedVariableProduct extends WC_Product_Variable
return 'licensed-variable';
}
/**
* Check if product is of a certain type
* Override to return true for 'variable' as well, so WooCommerce internal
* checks pass (many methods in WC_Product_Variable check is_type('variable'))
*/
public function is_type($type): bool
{
if (is_array($type)) {
return in_array($this->get_type(), $type, true) || in_array('variable', $type, true);
}
return $this->get_type() === $type || 'variable' === $type;
}
/**
* Licensed products are always virtual
*/
@@ -60,6 +73,189 @@ class LicensedVariableProduct extends WC_Product_Variable
return parent::is_purchasable();
}
/**
* Licensed products are always in stock (virtual, no inventory)
*/
public function is_in_stock(): bool
{
return true;
}
/**
* Get children (variations) for this product
* Override because WC_Product_Variable::get_children() checks is_type('variable')
* which fails for our 'licensed-variable' type
*/
public function get_children($context = 'view'): array
{
if (!$this->get_id()) {
return [];
}
// Query variations directly from database since WooCommerce's data store
// doesn't work properly with custom variable product types
global $wpdb;
$children = $wpdb->get_col($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_parent = %d
AND post_type = 'product_variation'
AND post_status IN ('publish', 'private')
ORDER BY menu_order ASC, ID ASC",
$this->get_id()
));
$children = array_map('intval', $children);
if ('view' === $context) {
$children = apply_filters('woocommerce_get_children', $children, $this, false);
}
return is_array($children) ? $children : [];
}
/**
* Get variation attributes for this product
* Override because WC_Product_Variable uses data_store which doesn't work
* properly with custom variable product types
*/
public function get_variation_attributes(): array
{
$attributes = $this->get_attributes();
if (!$attributes || !is_array($attributes)) {
return [];
}
$variation_attributes = [];
foreach ($attributes as $attribute) {
// For WC_Product_Attribute objects
if ($attribute instanceof \WC_Product_Attribute) {
if ($attribute->get_variation()) {
$attribute_name = $attribute->get_name();
// For taxonomy attributes, get term slugs
if ($attribute->is_taxonomy()) {
$attribute_terms = wc_get_product_terms(
$this->get_id(),
$attribute_name,
['fields' => 'slugs']
);
$variation_attributes[$attribute_name] = $attribute_terms;
} else {
// For custom attributes, get options directly
$variation_attributes[$attribute_name] = $attribute->get_options();
}
}
}
// For array-based attributes (older format)
elseif (is_array($attribute) && !empty($attribute['is_variation'])) {
$attribute_name = $attribute['name'];
$values = isset($attribute['value']) ? explode('|', $attribute['value']) : [];
$variation_attributes[$attribute_name] = array_map('trim', $values);
}
}
return $variation_attributes;
}
/**
* Get variation prices (regular, sale, and final prices)
* Override because WC_Product_Variable uses data_store which doesn't work
* properly with custom variable product types
*/
public function get_variation_prices($for_display = false): array
{
$children = $this->get_children();
if (empty($children)) {
return [
'price' => [],
'regular_price' => [],
'sale_price' => [],
];
}
$prices = [
'price' => [],
'regular_price' => [],
'sale_price' => [],
];
foreach ($children as $child_id) {
$variation = wc_get_product($child_id);
if ($variation) {
$price = $variation->get_price();
$regular_price = $variation->get_regular_price();
$sale_price = $variation->get_sale_price();
if ('' !== $price) {
$prices['price'][$child_id] = $price;
}
if ('' !== $regular_price) {
$prices['regular_price'][$child_id] = $regular_price;
}
if ('' !== $sale_price) {
$prices['sale_price'][$child_id] = $sale_price;
}
}
}
// Sort prices
asort($prices['price']);
asort($prices['regular_price']);
asort($prices['sale_price']);
$this->prices_array = $prices;
return $this->prices_array;
}
/**
* Get available variations for this product
* Override because WC_Product_Variable uses data_store which doesn't work
* properly with custom variable product types
*/
public function get_available_variations($return = 'array')
{
$children = $this->get_children();
$available_variations = [];
foreach ($children as $child_id) {
$variation = wc_get_product($child_id);
if (!$variation) {
continue;
}
// Check if variation should be available
if (!$variation->exists()) {
continue;
}
// Check if purchasable (has price)
if (!$variation->is_purchasable()) {
continue;
}
// Build variation data
if ($return === 'array') {
$variationData = $this->get_available_variation($variation);
// Override availability_html to be empty for licensed products
$variationData['availability_html'] = '';
$available_variations[] = $variationData;
} else {
$available_variations[] = $variation;
}
}
if ($return === 'array') {
$available_variations = array_values(array_filter($available_variations));
}
return $available_variations;
}
/**
* Get max activations for this product (parent default)
* Falls back to default settings if not set on product