All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
New modular component widgets: - Discount Ribbon (ribbon/pill/text styles) - Strain Badge (Sativa/Indica/Hybrid colored pills) - THC/CBD Meter (progress bars or badges) - Effects Display (styled chips with icons) - Price Block (original + sale price) - Cart Button (styled CTA linking to menu) - Stock Indicator (in/out of stock badges) - Product Image + Badges (image with overlays) New card template: - Premium Product Card (ready-to-use template) Extended dynamic tags (30+ total): - Discount %, Strain Badge, THC/CBD Badge - Effects Chips, Terpenes, Price Display - Menu URL, Stock Status, and more New files: - assets/css/components.css - includes/effects-icons.php (SVG icons) - 10 new widget files - dynamic-tags-extended.php Branding updated to "CannaiQ" throughout. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
511 lines
19 KiB
PHP
511 lines
19 KiB
PHP
<?php
|
|
/**
|
|
* CannaIQ Premium Card Template Widget
|
|
*
|
|
* Pre-built product card template showcasing all modular components.
|
|
* Includes: discount ribbon, product image with overlays, name, brand,
|
|
* effects, price block, and cart button.
|
|
*
|
|
* @package CannaIQ_Menus
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Ensure effects icons are loaded
|
|
require_once dirname(__DIR__) . '/includes/effects-icons.php';
|
|
|
|
class CannaIQ_Premium_Card_Widget extends \Elementor\Widget_Base {
|
|
|
|
public function get_name() {
|
|
return 'cannaiq_premium_card';
|
|
}
|
|
|
|
public function get_title() {
|
|
return __('Premium Product Card', 'cannaiq-menus');
|
|
}
|
|
|
|
public function get_icon() {
|
|
return 'eicon-single-product';
|
|
}
|
|
|
|
public function get_categories() {
|
|
return ['cannaiq'];
|
|
}
|
|
|
|
public function get_keywords() {
|
|
return ['product', 'card', 'premium', 'template', 'cannaiq'];
|
|
}
|
|
|
|
protected function register_controls() {
|
|
|
|
// Content Section
|
|
$this->start_controls_section(
|
|
'content_section',
|
|
[
|
|
'label' => __('Content', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'content_note',
|
|
[
|
|
'type' => \Elementor\Controls_Manager::RAW_HTML,
|
|
'raw' => __('This card uses the current product context from Product Loop or Product Grid. Place it inside a CannaiQ Product Loop widget.', 'cannaiq-menus'),
|
|
'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Components Section
|
|
$this->start_controls_section(
|
|
'components_section',
|
|
[
|
|
'label' => __('Components', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_discount',
|
|
[
|
|
'label' => __('Show Discount Ribbon', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_image',
|
|
[
|
|
'label' => __('Show Product Image', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_strain_badge',
|
|
[
|
|
'label' => __('Show Strain Badge', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_thc_badge',
|
|
[
|
|
'label' => __('Show THC Badge', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_name',
|
|
[
|
|
'label' => __('Show Product Name', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_brand',
|
|
[
|
|
'label' => __('Show Brand', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_effects',
|
|
[
|
|
'label' => __('Show Effects', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'effects_limit',
|
|
[
|
|
'label' => __('Effects Limit', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => 3,
|
|
'min' => 1,
|
|
'max' => 5,
|
|
'condition' => [
|
|
'show_effects' => 'yes',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_price',
|
|
[
|
|
'label' => __('Show Price', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_weight',
|
|
[
|
|
'label' => __('Show Weight', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_cart_button',
|
|
[
|
|
'label' => __('Show Cart Button', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'label_on' => __('Yes', 'cannaiq-menus'),
|
|
'label_off' => __('No', 'cannaiq-menus'),
|
|
'return_value' => 'yes',
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'button_text',
|
|
[
|
|
'label' => __('Button Text', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::TEXT,
|
|
'default' => 'ADD TO CART',
|
|
'condition' => [
|
|
'show_cart_button' => 'yes',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Style Section
|
|
$this->start_controls_section(
|
|
'card_style_section',
|
|
[
|
|
'label' => __('Card Style', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'card_background',
|
|
[
|
|
'label' => __('Background', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#ffffff',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card' => 'background-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'card_border_radius',
|
|
[
|
|
'label' => __('Border Radius', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SLIDER,
|
|
'size_units' => ['px'],
|
|
'range' => [
|
|
'px' => [
|
|
'min' => 0,
|
|
'max' => 30,
|
|
],
|
|
],
|
|
'default' => [
|
|
'size' => 12,
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card' => 'border-radius: {{SIZE}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'card_padding',
|
|
[
|
|
'label' => __('Padding', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SLIDER,
|
|
'size_units' => ['px'],
|
|
'range' => [
|
|
'px' => [
|
|
'min' => 0,
|
|
'max' => 40,
|
|
],
|
|
],
|
|
'default' => [
|
|
'size' => 16,
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card__body' => 'padding: {{SIZE}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Box_Shadow::get_type(),
|
|
[
|
|
'name' => 'card_shadow',
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-card',
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Typography Section
|
|
$this->start_controls_section(
|
|
'typography_section',
|
|
[
|
|
'label' => __('Typography', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Typography::get_type(),
|
|
[
|
|
'name' => 'title_typography',
|
|
'label' => __('Title', 'cannaiq-menus'),
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-card__title',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'title_color',
|
|
[
|
|
'label' => __('Title Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#1f2937',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card__title' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'brand_color',
|
|
[
|
|
'label' => __('Brand Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#6b7280',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card__brand' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Button Style Section
|
|
$this->start_controls_section(
|
|
'button_style_section',
|
|
[
|
|
'label' => __('Button Style', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
'condition' => [
|
|
'show_cart_button' => 'yes',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'button_background',
|
|
[
|
|
'label' => __('Background', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#1f2937',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-cart-button' => 'background-color: {{VALUE}}; border-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'button_text_color',
|
|
[
|
|
'label' => __('Text Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#ffffff',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-cart-button' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'button_hover_background',
|
|
[
|
|
'label' => __('Hover Background', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#374151',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-cart-button:hover' => 'background-color: {{VALUE}}; border-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
}
|
|
|
|
protected function render() {
|
|
$settings = $this->get_settings_for_display();
|
|
|
|
global $cannaiq_current_product;
|
|
$product = $cannaiq_current_product ?? [];
|
|
|
|
if (empty($product)) {
|
|
echo '<p>' . __('No product context available. Place this widget inside a Product Loop.', 'cannaiq-menus') . '</p>';
|
|
return;
|
|
}
|
|
|
|
// Extract product data
|
|
$name = $product['Name'] ?? $product['name'] ?? '';
|
|
$brand = $product['brand']['name'] ?? $product['brandName'] ?? $product['brand'] ?? '';
|
|
$image_url = $product['Image'] ?? $product['images'][0]['url'] ?? $product['image_url'] ?? '';
|
|
$strain_type = strtolower($product['strainType'] ?? $product['strain_type'] ?? '');
|
|
$thc = $product['THCContent']['range'][0] ?? $product['THC'] ?? $product['thc_percentage'] ?? null;
|
|
$weight = $product['Options'][0] ?? $product['rawOptions'][0] ?? $product['weight'] ?? '';
|
|
$menu_url = $product['menuUrl'] ?? $product['menu_url'] ?? $product['productUrl'] ?? '#';
|
|
|
|
// Price
|
|
$original_price = $product['Prices'][0] ?? $product['regular_price'] ?? null;
|
|
$sale_price = $product['specialPrice'] ?? $product['sale_price'] ?? null;
|
|
$is_on_sale = $sale_price && $sale_price > 0 && $sale_price < $original_price;
|
|
$discount_percent = 0;
|
|
if ($is_on_sale && $original_price > 0) {
|
|
$discount_percent = round((($original_price - $sale_price) / $original_price) * 100);
|
|
}
|
|
|
|
// Effects
|
|
$effects = $product['effects'] ?? [];
|
|
if (!empty($effects) && !isset($effects[0])) {
|
|
arsort($effects);
|
|
$effects = array_keys($effects);
|
|
}
|
|
$effects_limit = intval($settings['effects_limit']) ?: 3;
|
|
$effects = array_slice($effects, 0, $effects_limit);
|
|
|
|
// Strain colors
|
|
$strain_colors = [
|
|
'sativa' => '#22c55e',
|
|
'indica' => '#8b5cf6',
|
|
'hybrid' => '#f97316',
|
|
];
|
|
|
|
?>
|
|
<div class="cannaiq-product-card">
|
|
<!-- Image Section -->
|
|
<?php if ($settings['show_image'] === 'yes'): ?>
|
|
<div class="cannaiq-product-card__image">
|
|
<div class="cannaiq-product-image" style="aspect-ratio: 1;">
|
|
<?php if (!empty($image_url)): ?>
|
|
<img src="<?php echo esc_url($image_url); ?>" alt="<?php echo esc_attr($name); ?>" />
|
|
<?php endif; ?>
|
|
|
|
<!-- Discount Ribbon -->
|
|
<?php if ($settings['show_discount'] === 'yes' && $discount_percent > 0): ?>
|
|
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--top-left">
|
|
<span class="cannaiq-discount-ribbon cannaiq-discount-ribbon--ribbon"><?php echo esc_html($discount_percent); ?>% OFF</span>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Bottom badges -->
|
|
<?php if (($settings['show_strain_badge'] === 'yes' && !empty($strain_type)) || ($settings['show_thc_badge'] === 'yes' && $thc > 0)): ?>
|
|
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--bottom-left">
|
|
<div class="cannaiq-product-image__badges">
|
|
<?php if ($settings['show_strain_badge'] === 'yes' && !empty($strain_type) && in_array($strain_type, ['sativa', 'indica', 'hybrid'])): ?>
|
|
<span class="cannaiq-strain-badge cannaiq-strain-badge--pill" style="background-color: <?php echo esc_attr($strain_colors[$strain_type]); ?>; color: white;"><?php echo esc_html(strtoupper($strain_type)); ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($settings['show_thc_badge'] === 'yes' && $thc > 0): ?>
|
|
<span class="cannaiq-potency-badge cannaiq-potency-badge--pill" style="background-color: #1f2937; color: white;"><?php echo esc_html(number_format((float)$thc, 1)); ?>% THC</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Body Section -->
|
|
<div class="cannaiq-product-card__body">
|
|
<?php if ($settings['show_name'] === 'yes' && !empty($name)): ?>
|
|
<h3 class="cannaiq-product-card__title"><?php echo esc_html($name); ?></h3>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_brand'] === 'yes' && !empty($brand)): ?>
|
|
<p class="cannaiq-product-card__brand">by <?php echo esc_html($brand); ?></p>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_effects'] === 'yes' && !empty($effects)): ?>
|
|
<div style="margin: 12px 0;">
|
|
<?php echo cannaiq_render_effects($effects, ['limit' => $effects_limit, 'show_icon' => true, 'size' => 'small']); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Footer -->
|
|
<div class="cannaiq-product-card__footer">
|
|
<?php if ($settings['show_price'] === 'yes' && $original_price > 0): ?>
|
|
<div class="cannaiq-price-block" style="margin-bottom: 12px;">
|
|
<?php if ($settings['show_weight'] === 'yes' && !empty($weight)): ?>
|
|
<span class="cannaiq-price-block__weight"><?php echo esc_html($weight); ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($is_on_sale): ?>
|
|
<span class="cannaiq-price-block__original">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
|
|
<span class="cannaiq-price-block__sale">$<?php echo esc_html(number_format((float)$sale_price, 2)); ?></span>
|
|
<?php else: ?>
|
|
<span class="cannaiq-price-block__regular">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_cart_button'] === 'yes'): ?>
|
|
<a href="<?php echo esc_url($menu_url); ?>" class="cannaiq-cart-button cannaiq-cart-button--solid cannaiq-cart-button--full" target="_blank" rel="noopener noreferrer">
|
|
<?php echo esc_html($settings['button_text']); ?>
|
|
</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
}
|