WordPress Plugin: - Add dynamic tags for all product payload fields (name, brand, price, THC, effects, etc.) - Add Product Loop widget with filtering, sorting, and layout options - Register CannaIQ widget category in Elementor - Update build script to auto-upload to MinIO CDN - Remove legacy dutchie references - Bump version to 1.7.0 Backend: - Redirect /downloads/* to CDN instead of serving from local filesystem 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
859 lines
29 KiB
PHP
859 lines
29 KiB
PHP
<?php
|
|
/**
|
|
* CannaIQ Product Loop Widget
|
|
*
|
|
* Elementor widget that loops through products and renders each one.
|
|
* Sets the global $cannaiq_current_product for each iteration so dynamic tags work.
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class CannaIQ_Product_Loop_Widget extends \Elementor\Widget_Base {
|
|
|
|
public function get_name() {
|
|
return 'cannaiq-product-loop';
|
|
}
|
|
|
|
public function get_title() {
|
|
return __('CannaIQ Product Loop', 'cannaiq-menus');
|
|
}
|
|
|
|
public function get_icon() {
|
|
return 'eicon-loop-builder';
|
|
}
|
|
|
|
public function get_categories() {
|
|
return ['cannaiq'];
|
|
}
|
|
|
|
public function get_keywords() {
|
|
return ['cannaiq', 'products', 'loop', 'menu', 'cannabis', 'dispensary'];
|
|
}
|
|
|
|
protected function register_controls() {
|
|
|
|
// Content Section - Data Source
|
|
$this->start_controls_section(
|
|
'section_data_source',
|
|
[
|
|
'label' => __('Data Source', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'dispensary_id',
|
|
[
|
|
'label' => __('Store ID', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => '',
|
|
'description' => __('Enter your store/dispensary ID from CannaIQ', 'cannaiq-menus'),
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'limit',
|
|
[
|
|
'label' => __('Products Limit', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => 12,
|
|
'min' => 1,
|
|
'max' => 100,
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Content Section - Filters
|
|
$this->start_controls_section(
|
|
'section_filters',
|
|
[
|
|
'label' => __('Filters', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'category',
|
|
[
|
|
'label' => __('Category', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => '',
|
|
'options' => [
|
|
'' => __('All Categories', 'cannaiq-menus'),
|
|
'Flower' => __('Flower', 'cannaiq-menus'),
|
|
'Vaporizers' => __('Vaporizers', 'cannaiq-menus'),
|
|
'Concentrates' => __('Concentrates', 'cannaiq-menus'),
|
|
'Edibles' => __('Edibles', 'cannaiq-menus'),
|
|
'Pre-Rolls' => __('Pre-Rolls', 'cannaiq-menus'),
|
|
'Topicals' => __('Topicals', 'cannaiq-menus'),
|
|
'Tinctures' => __('Tinctures', 'cannaiq-menus'),
|
|
'Accessories' => __('Accessories', 'cannaiq-menus'),
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'strain_type',
|
|
[
|
|
'label' => __('Strain Type', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => '',
|
|
'options' => [
|
|
'' => __('All Strains', 'cannaiq-menus'),
|
|
'sativa' => __('Sativa', 'cannaiq-menus'),
|
|
'indica' => __('Indica', 'cannaiq-menus'),
|
|
'hybrid' => __('Hybrid', 'cannaiq-menus'),
|
|
'cbd' => __('CBD', 'cannaiq-menus'),
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'brand',
|
|
[
|
|
'label' => __('Brand Name', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::TEXT,
|
|
'default' => '',
|
|
'description' => __('Filter by brand name (exact match)', 'cannaiq-menus'),
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'on_sale',
|
|
[
|
|
'label' => __('On Sale Only', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => '',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'min_thc',
|
|
[
|
|
'label' => __('Minimum THC %', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => '',
|
|
'min' => 0,
|
|
'max' => 100,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'max_price',
|
|
[
|
|
'label' => __('Maximum Price', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => '',
|
|
'min' => 0,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'search',
|
|
[
|
|
'label' => __('Search Term', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::TEXT,
|
|
'default' => '',
|
|
'description' => __('Search in product name, brand, description', 'cannaiq-menus'),
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'sort_by',
|
|
[
|
|
'label' => __('Sort By', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => 'name',
|
|
'options' => [
|
|
'name' => __('Name (A-Z)', 'cannaiq-menus'),
|
|
'name_desc' => __('Name (Z-A)', 'cannaiq-menus'),
|
|
'price' => __('Price (Low to High)', 'cannaiq-menus'),
|
|
'price_desc' => __('Price (High to Low)', 'cannaiq-menus'),
|
|
'thc' => __('THC % (Low to High)', 'cannaiq-menus'),
|
|
'thc_desc' => __('THC % (High to Low)', 'cannaiq-menus'),
|
|
'brand' => __('Brand (A-Z)', 'cannaiq-menus'),
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Layout Section
|
|
$this->start_controls_section(
|
|
'section_layout',
|
|
[
|
|
'label' => __('Layout', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'layout',
|
|
[
|
|
'label' => __('Layout', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => 'grid',
|
|
'options' => [
|
|
'grid' => __('Grid', 'cannaiq-menus'),
|
|
'list' => __('List', 'cannaiq-menus'),
|
|
'masonry' => __('Masonry', 'cannaiq-menus'),
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_responsive_control(
|
|
'columns',
|
|
[
|
|
'label' => __('Columns', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => '3',
|
|
'tablet_default' => '2',
|
|
'mobile_default' => '1',
|
|
'options' => [
|
|
'1' => '1',
|
|
'2' => '2',
|
|
'3' => '3',
|
|
'4' => '4',
|
|
'5' => '5',
|
|
'6' => '6',
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-loop-grid' => 'grid-template-columns: repeat({{VALUE}}, 1fr);',
|
|
],
|
|
'condition' => [
|
|
'layout' => ['grid', 'masonry'],
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_responsive_control(
|
|
'gap',
|
|
[
|
|
'label' => __('Gap', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SLIDER,
|
|
'size_units' => ['px', 'em', 'rem'],
|
|
'range' => [
|
|
'px' => ['min' => 0, 'max' => 100],
|
|
],
|
|
'default' => ['size' => 20, 'unit' => 'px'],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-loop-grid' => 'gap: {{SIZE}}{{UNIT}};',
|
|
'{{WRAPPER}} .cannaiq-product-loop-list' => 'gap: {{SIZE}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Card Display Section
|
|
$this->start_controls_section(
|
|
'section_card_display',
|
|
[
|
|
'label' => __('Card Display', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_image',
|
|
[
|
|
'label' => __('Show Image', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_brand',
|
|
[
|
|
'label' => __('Show Brand', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_name',
|
|
[
|
|
'label' => __('Show Name', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_category',
|
|
[
|
|
'label' => __('Show Category', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_strain_type',
|
|
[
|
|
'label' => __('Show Strain Type', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_thc',
|
|
[
|
|
'label' => __('Show THC %', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_cbd',
|
|
[
|
|
'label' => __('Show CBD %', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => '',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_effects',
|
|
[
|
|
'label' => __('Show Effects', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'effects_limit',
|
|
[
|
|
'label' => __('Effects Limit', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
|
'default' => 3,
|
|
'min' => 1,
|
|
'max' => 10,
|
|
'condition' => [
|
|
'show_effects' => 'yes',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_price',
|
|
[
|
|
'label' => __('Show Price', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_weight',
|
|
[
|
|
'label' => __('Show Weight/Size', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'show_sale_badge',
|
|
[
|
|
'label' => __('Show Sale Badge', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SWITCHER,
|
|
'default' => 'yes',
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Style Section - Card
|
|
$this->start_controls_section(
|
|
'section_style_card',
|
|
[
|
|
'label' => __('Card', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'card_background',
|
|
[
|
|
'label' => __('Background Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#ffffff',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card' => 'background-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_responsive_control(
|
|
'card_padding',
|
|
[
|
|
'label' => __('Padding', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::DIMENSIONS,
|
|
'size_units' => ['px', 'em', '%'],
|
|
'default' => [
|
|
'top' => '15',
|
|
'right' => '15',
|
|
'bottom' => '15',
|
|
'left' => '15',
|
|
'unit' => 'px',
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Border::get_type(),
|
|
[
|
|
'name' => 'card_border',
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-card',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'card_border_radius',
|
|
[
|
|
'label' => __('Border Radius', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::DIMENSIONS,
|
|
'size_units' => ['px', '%'],
|
|
'default' => [
|
|
'top' => '8',
|
|
'right' => '8',
|
|
'bottom' => '8',
|
|
'left' => '8',
|
|
'unit' => 'px',
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-card' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Box_Shadow::get_type(),
|
|
[
|
|
'name' => 'card_shadow',
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-card',
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Style Section - Image
|
|
$this->start_controls_section(
|
|
'section_style_image',
|
|
[
|
|
'label' => __('Image', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_responsive_control(
|
|
'image_height',
|
|
[
|
|
'label' => __('Height', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SLIDER,
|
|
'size_units' => ['px', 'vh'],
|
|
'range' => [
|
|
'px' => ['min' => 100, 'max' => 500],
|
|
],
|
|
'default' => ['size' => 200, 'unit' => 'px'],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-image' => 'height: {{SIZE}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'image_fit',
|
|
[
|
|
'label' => __('Object Fit', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::SELECT,
|
|
'default' => 'contain',
|
|
'options' => [
|
|
'contain' => __('Contain', 'cannaiq-menus'),
|
|
'cover' => __('Cover', 'cannaiq-menus'),
|
|
'fill' => __('Fill', 'cannaiq-menus'),
|
|
],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-image img' => 'object-fit: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'image_border_radius',
|
|
[
|
|
'label' => __('Border Radius', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::DIMENSIONS,
|
|
'size_units' => ['px', '%'],
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-image' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Style Section - Typography
|
|
$this->start_controls_section(
|
|
'section_style_typography',
|
|
[
|
|
'label' => __('Typography', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'name_color',
|
|
[
|
|
'label' => __('Name Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#1a1a1a',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-name' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Typography::get_type(),
|
|
[
|
|
'name' => 'name_typography',
|
|
'label' => __('Name Typography', 'cannaiq-menus'),
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-name',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'brand_color',
|
|
[
|
|
'label' => __('Brand Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#666666',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-brand' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'price_color',
|
|
[
|
|
'label' => __('Price Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#2ecc71',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-product-price' => 'color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_group_control(
|
|
\Elementor\Group_Control_Typography::get_type(),
|
|
[
|
|
'name' => 'price_typography',
|
|
'label' => __('Price Typography', 'cannaiq-menus'),
|
|
'selector' => '{{WRAPPER}} .cannaiq-product-price',
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
|
|
// Style Section - Badges
|
|
$this->start_controls_section(
|
|
'section_style_badges',
|
|
[
|
|
'label' => __('Badges', 'cannaiq-menus'),
|
|
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'strain_sativa_color',
|
|
[
|
|
'label' => __('Sativa Badge Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#f39c12',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'strain_indica_color',
|
|
[
|
|
'label' => __('Indica Badge Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#9b59b6',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'strain_hybrid_color',
|
|
[
|
|
'label' => __('Hybrid Badge Color', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#2ecc71',
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'sale_badge_color',
|
|
[
|
|
'label' => __('Sale Badge Background', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#e74c3c',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-sale-badge' => 'background-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->add_control(
|
|
'effect_badge_background',
|
|
[
|
|
'label' => __('Effect Badge Background', 'cannaiq-menus'),
|
|
'type' => \Elementor\Controls_Manager::COLOR,
|
|
'default' => '#f0f0f0',
|
|
'selectors' => [
|
|
'{{WRAPPER}} .cannaiq-effect-badge' => 'background-color: {{VALUE}};',
|
|
],
|
|
]
|
|
);
|
|
|
|
$this->end_controls_section();
|
|
}
|
|
|
|
protected function render() {
|
|
$settings = $this->get_settings_for_display();
|
|
$products = $this->fetch_products($settings);
|
|
|
|
if (empty($products)) {
|
|
echo '<p class="cannaiq-no-products">' . __('No products found.', 'cannaiq-menus') . '</p>';
|
|
return;
|
|
}
|
|
|
|
$layout_class = 'cannaiq-product-loop-' . $settings['layout'];
|
|
?>
|
|
<div class="cannaiq-product-loop <?php echo esc_attr($layout_class); ?>">
|
|
<?php
|
|
global $cannaiq_current_product;
|
|
|
|
foreach ($products as $product) {
|
|
// Set global for dynamic tags
|
|
$cannaiq_current_product = $product;
|
|
|
|
$this->render_product_card($product, $settings);
|
|
}
|
|
|
|
// Clear global
|
|
$cannaiq_current_product = null;
|
|
?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
protected function render_product_card($product, $settings) {
|
|
// Support both normalized API format and raw payload format
|
|
$strain_type = strtolower($product['strain_type'] ?? $product['strainType'] ?? '');
|
|
$strain_colors = [
|
|
'sativa' => $settings['strain_sativa_color'],
|
|
'indica' => $settings['strain_indica_color'],
|
|
'hybrid' => $settings['strain_hybrid_color'],
|
|
];
|
|
$strain_color = $strain_colors[$strain_type] ?? '#999999';
|
|
|
|
// Get values supporting both formats
|
|
$product_id = $product['id'] ?? $product['_id'] ?? '';
|
|
$product_name = $product['name'] ?? $product['Name'] ?? '';
|
|
$brand_name = $product['brand'] ?? $product['brandName'] ?? '';
|
|
if (is_array($brand_name)) {
|
|
$brand_name = $brand_name['name'] ?? '';
|
|
}
|
|
$category = $product['category'] ?? $product['type'] ?? $product['Category'] ?? '';
|
|
$image_url = $product['image_url'] ?? $product['Image'] ?? $product['images'][0]['url'] ?? '';
|
|
$weight = $product['weight'] ?? $product['Options'][0] ?? $product['rawOptions'][0] ?? '';
|
|
$thc = $product['thc'] ?? $product['THCContent']['range'][0] ?? $product['THC'] ?? null;
|
|
$cbd = $product['cbd'] ?? $product['CBDContent']['range'][0] ?? $product['CBD'] ?? null;
|
|
$price = $product['price'] ?? $product['Prices'][0] ?? $product['recPrices'][0] ?? null;
|
|
if (is_array($price)) {
|
|
$price = $price['price'] ?? $price[0] ?? null;
|
|
}
|
|
$on_sale = $product['special'] ?? $product['on_sale'] ?? false;
|
|
$in_stock = $product['in_stock'] ?? ($product['status'] ?? $product['Status']) === 'Active';
|
|
?>
|
|
<div class="cannaiq-product-card" data-product-id="<?php echo esc_attr($product_id); ?>">
|
|
|
|
<?php if ($settings['show_sale_badge'] === 'yes' && $on_sale): ?>
|
|
<div class="cannaiq-sale-badge">SALE</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_image'] === 'yes'): ?>
|
|
<div class="cannaiq-product-image">
|
|
<?php if ($image_url): ?>
|
|
<img src="<?php echo esc_url($image_url); ?>" alt="<?php echo esc_attr($product_name); ?>" loading="lazy" />
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="cannaiq-product-content">
|
|
|
|
<?php if ($settings['show_brand'] === 'yes' && $brand_name): ?>
|
|
<div class="cannaiq-product-brand">
|
|
<?php echo esc_html($brand_name); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_name'] === 'yes'): ?>
|
|
<h3 class="cannaiq-product-name">
|
|
<?php echo esc_html($product_name); ?>
|
|
</h3>
|
|
<?php endif; ?>
|
|
|
|
<div class="cannaiq-product-meta">
|
|
<?php if ($settings['show_category'] === 'yes' && $category): ?>
|
|
<span class="cannaiq-product-category"><?php echo esc_html($category); ?></span>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_strain_type'] === 'yes' && $strain_type): ?>
|
|
<span class="cannaiq-strain-badge" style="background-color: <?php echo esc_attr($strain_color); ?>;">
|
|
<?php echo esc_html(ucfirst($strain_type)); ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_weight'] === 'yes' && $weight): ?>
|
|
<span class="cannaiq-product-weight"><?php echo esc_html($weight); ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if ($settings['show_thc'] === 'yes' || $settings['show_cbd'] === 'yes'): ?>
|
|
<div class="cannaiq-cannabinoids">
|
|
<?php if ($settings['show_thc'] === 'yes' && $thc !== null): ?>
|
|
<span class="cannaiq-thc">THC: <?php echo number_format((float)$thc, 1); ?>%</span>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_cbd'] === 'yes' && $cbd !== null): ?>
|
|
<span class="cannaiq-cbd">CBD: <?php echo number_format((float)$cbd, 1); ?>%</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_effects'] === 'yes' && !empty($product['effects'])): ?>
|
|
<div class="cannaiq-effects">
|
|
<?php
|
|
$effects = $product['effects'];
|
|
if (is_array($effects)) {
|
|
arsort($effects);
|
|
$effects = array_slice($effects, 0, (int)$settings['effects_limit'], true);
|
|
foreach ($effects as $effect => $score):
|
|
?>
|
|
<span class="cannaiq-effect-badge"><?php echo esc_html(ucfirst($effect)); ?></span>
|
|
<?php
|
|
endforeach;
|
|
}
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($settings['show_price'] === 'yes' && $price !== null): ?>
|
|
<div class="cannaiq-product-price">
|
|
<?php echo '$' . number_format((float)$price, 2); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
protected function fetch_products($settings) {
|
|
$dispensary_id = $settings['dispensary_id'];
|
|
|
|
if (empty($dispensary_id)) {
|
|
return [];
|
|
}
|
|
|
|
// Build API URL - use the payload query endpoint
|
|
$api_url = CANNAIQ_MENUS_API_URL . '/payloads/store/' . intval($dispensary_id) . '/query';
|
|
|
|
// Build query parameters matching the API expected format
|
|
$params = [];
|
|
|
|
if (!empty($settings['category'])) {
|
|
$params['category'] = $settings['category'];
|
|
}
|
|
|
|
if (!empty($settings['strain_type'])) {
|
|
$params['strain_type'] = $settings['strain_type'];
|
|
}
|
|
|
|
if (!empty($settings['brand'])) {
|
|
$params['brand'] = $settings['brand'];
|
|
}
|
|
|
|
if (!empty($settings['search'])) {
|
|
$params['search'] = $settings['search'];
|
|
}
|
|
|
|
if (!empty($settings['min_thc'])) {
|
|
$params['thc_min'] = $settings['min_thc'];
|
|
}
|
|
|
|
if (!empty($settings['max_price'])) {
|
|
$params['price_max'] = $settings['max_price'];
|
|
}
|
|
|
|
// Only show in-stock by default
|
|
$params['in_stock'] = 'true';
|
|
|
|
if (!empty($settings['limit'])) {
|
|
$params['limit'] = $settings['limit'];
|
|
}
|
|
|
|
// Handle sorting
|
|
$sort_map = [
|
|
'name' => ['sort' => 'name', 'order' => 'asc'],
|
|
'name_desc' => ['sort' => 'name', 'order' => 'desc'],
|
|
'price' => ['sort' => 'price', 'order' => 'asc'],
|
|
'price_desc' => ['sort' => 'price', 'order' => 'desc'],
|
|
'thc' => ['sort' => 'thc', 'order' => 'asc'],
|
|
'thc_desc' => ['sort' => 'thc', 'order' => 'desc'],
|
|
'brand' => ['sort' => 'brand', 'order' => 'asc'],
|
|
];
|
|
|
|
if (!empty($settings['sort_by']) && isset($sort_map[$settings['sort_by']])) {
|
|
$params['sort'] = $sort_map[$settings['sort_by']]['sort'];
|
|
$params['order'] = $sort_map[$settings['sort_by']]['order'];
|
|
}
|
|
|
|
$query_string = http_build_query($params);
|
|
$url = $api_url . ($query_string ? '?' . $query_string : '');
|
|
|
|
// Make request
|
|
$response = wp_remote_get($url, [
|
|
'timeout' => 30,
|
|
'headers' => [
|
|
'Accept' => 'application/json',
|
|
],
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return [];
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['products'] ?? [];
|
|
}
|
|
}
|