feat(plugin): Add Elementor dynamic tags and product loop widget v1.7.0
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>
This commit is contained in:
@@ -38,42 +38,16 @@ app.use('/images', express.static(LOCAL_IMAGES_PATH));
|
|||||||
// Usage: /img/products/az/store/brand/product/image.webp?w=200&h=200
|
// Usage: /img/products/az/store/brand/product/image.webp?w=200&h=200
|
||||||
app.use('/img', imageProxyRoutes);
|
app.use('/img', imageProxyRoutes);
|
||||||
|
|
||||||
// Serve static downloads (plugin files, etc.)
|
// Serve downloads from MinIO/CDN
|
||||||
// Uses ./public/downloads relative to working directory (works for both Docker and local dev)
|
// Files are stored in MinIO bucket at: cannaiq/downloads/
|
||||||
const LOCAL_DOWNLOADS_PATH = process.env.LOCAL_DOWNLOADS_PATH || './public/downloads';
|
const CDN_DOWNLOADS_URL = process.env.CDN_DOWNLOADS_URL || 'https://cdn.cannabrands.app/cannaiq/downloads';
|
||||||
|
|
||||||
// Dynamic "latest" redirect for WordPress plugin - finds highest version automatically
|
// Redirect all download requests to CDN
|
||||||
app.get('/downloads/cannaiq-menus-latest.zip', (req, res) => {
|
app.get('/downloads/:filename', (req, res) => {
|
||||||
const fs = require('fs');
|
const filename = req.params.filename;
|
||||||
const path = require('path');
|
res.redirect(302, `${CDN_DOWNLOADS_URL}/${filename}`);
|
||||||
try {
|
|
||||||
const files = fs.readdirSync(LOCAL_DOWNLOADS_PATH);
|
|
||||||
const pluginFiles = files
|
|
||||||
.filter((f: string) => f.match(/^cannaiq-menus-\d+\.\d+\.\d+\.zip$/))
|
|
||||||
.sort((a: string, b: string) => {
|
|
||||||
const vA = a.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
||||||
const vB = b.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
||||||
if (!vA || !vB) return 0;
|
|
||||||
for (let i = 1; i <= 3; i++) {
|
|
||||||
const diff = parseInt(vB[i]) - parseInt(vA[i]);
|
|
||||||
if (diff !== 0) return diff;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pluginFiles.length > 0) {
|
|
||||||
const latestFile = pluginFiles[0];
|
|
||||||
res.redirect(302, `/downloads/${latestFile}`);
|
|
||||||
} else {
|
|
||||||
res.status(404).json({ error: 'No plugin versions found' });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: 'Failed to find latest plugin' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/downloads', express.static(LOCAL_DOWNLOADS_PATH));
|
|
||||||
|
|
||||||
// Simple health check for load balancers/K8s probes
|
// Simple health check for load balancers/K8s probes
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.6.0
|
1.7.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* CannaIQ Menus - WordPress Plugin Styles
|
* CannaIQ Menus - WordPress Plugin Styles
|
||||||
* v1.5.3
|
* v1.7.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Product Grid */
|
/* Product Grid */
|
||||||
@@ -493,3 +493,189 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Product Loop Widget
|
||||||
|
======================================== */
|
||||||
|
.cannaiq-product-loop {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-list .cannaiq-product-card {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-list .cannaiq-product-image {
|
||||||
|
width: 150px;
|
||||||
|
min-width: 150px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-masonry {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop .cannaiq-product-card {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop .cannaiq-product-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop .cannaiq-product-category {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop .cannaiq-product-weight {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strain Badges */
|
||||||
|
.cannaiq-strain-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cannabinoid Display */
|
||||||
|
.cannaiq-cannabinoids {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-cannabinoids .cannaiq-thc,
|
||||||
|
.cannaiq-cannabinoids .cannaiq-cbd {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effects Badges */
|
||||||
|
.cannaiq-effects {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-effect-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #444;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dynamic Tag Effects Badges */
|
||||||
|
.cannaiq-effects-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-effects-badges .cannaiq-effect-badge {
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: linear-gradient(135deg, #f5f5f5, #e8e8e8);
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effect-specific colors */
|
||||||
|
.cannaiq-effect-happy { background: linear-gradient(135deg, #fff3cd, #ffeaa7); color: #856404; }
|
||||||
|
.cannaiq-effect-relaxed { background: linear-gradient(135deg, #d4edda, #c3e6cb); color: #155724; }
|
||||||
|
.cannaiq-effect-sleepy { background: linear-gradient(135deg, #e2e3f3, #c8c9e8); color: #383d6e; }
|
||||||
|
.cannaiq-effect-focused { background: linear-gradient(135deg, #cce5ff, #b8daff); color: #004085; }
|
||||||
|
.cannaiq-effect-creative { background: linear-gradient(135deg, #f8d7da, #f5c6cb); color: #721c24; }
|
||||||
|
.cannaiq-effect-energetic { background: linear-gradient(135deg, #ffecb3, #ffe082); color: #7c5e00; }
|
||||||
|
.cannaiq-effect-hungry { background: linear-gradient(135deg, #ffe0cc, #ffcc99); color: #8b4513; }
|
||||||
|
.cannaiq-effect-euphoric { background: linear-gradient(135deg, #e8daef, #d7bde2); color: #5b2c6f; }
|
||||||
|
.cannaiq-effect-uplifted { background: linear-gradient(135deg, #d1ecf1, #bee5eb); color: #0c5460; }
|
||||||
|
.cannaiq-effect-talkative { background: linear-gradient(135deg, #cce5ff, #99ccff); color: #004085; }
|
||||||
|
|
||||||
|
/* Sale Badge */
|
||||||
|
.cannaiq-sale-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background: #e74c3c;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No Products Message */
|
||||||
|
.cannaiq-no-products {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.cannaiq-product-loop-grid,
|
||||||
|
.cannaiq-product-loop-masonry {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.cannaiq-product-loop-grid,
|
||||||
|
.cannaiq-product-loop-masonry {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-list .cannaiq-product-card {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannaiq-product-loop-list .cannaiq-product-image {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# WordPress Plugin Build Script
|
# WordPress Plugin Build Script
|
||||||
# Builds the plugin zip with the correct naming convention: cannaiq-menus-{version}.zip
|
# Builds the plugin zip and uploads to MinIO CDN
|
||||||
|
#
|
||||||
|
# Usage: ./build-plugin.sh
|
||||||
|
#
|
||||||
|
# This script:
|
||||||
|
# 1. Extracts version from cannaiq-menus.php
|
||||||
|
# 2. Creates ZIP file excluding legacy/dev files
|
||||||
|
# 3. Uploads to MinIO CDN (cannaiq bucket, downloads/ folder)
|
||||||
|
# 4. Uploads as both versioned and "latest" files
|
||||||
|
#
|
||||||
|
# The plugin is then available at:
|
||||||
|
# https://cdn.cannabrands.app/cannaiq/downloads/cannaiq-menus-{version}.zip
|
||||||
|
# https://cdn.cannabrands.app/cannaiq/downloads/cannaiq-menus-latest.zip
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -36,7 +48,7 @@ zip -r "${OUTPUT_DIR}/${OUTPUT_FILE}" . \
|
|||||||
-x "assets/css/crawlsy-menus.css" \
|
-x "assets/css/crawlsy-menus.css" \
|
||||||
-x "assets/js/crawlsy-menus.js"
|
-x "assets/js/crawlsy-menus.js"
|
||||||
|
|
||||||
# Create/update the "latest" symlink
|
# Create/update the "latest" symlink (for local reference)
|
||||||
cd "${OUTPUT_DIR}"
|
cd "${OUTPUT_DIR}"
|
||||||
rm -f cannaiq-menus-latest.zip
|
rm -f cannaiq-menus-latest.zip
|
||||||
ln -s "${OUTPUT_FILE}" cannaiq-menus-latest.zip
|
ln -s "${OUTPUT_FILE}" cannaiq-menus-latest.zip
|
||||||
@@ -45,7 +57,42 @@ echo ""
|
|||||||
echo "Build complete!"
|
echo "Build complete!"
|
||||||
echo " File: ${OUTPUT_DIR}/${OUTPUT_FILE}"
|
echo " File: ${OUTPUT_DIR}/${OUTPUT_FILE}"
|
||||||
echo " Size: $(ls -lh "${OUTPUT_DIR}/${OUTPUT_FILE}" | awk '{print $5}')"
|
echo " Size: $(ls -lh "${OUTPUT_DIR}/${OUTPUT_FILE}" | awk '{print $5}')"
|
||||||
|
|
||||||
|
# Upload to MinIO CDN
|
||||||
echo ""
|
echo ""
|
||||||
echo "Download URLs:"
|
echo "Uploading to MinIO CDN..."
|
||||||
echo " Versioned: https://cannaiq.co/downloads/cannaiq-menus-${VERSION}.zip"
|
cd "${PLUGIN_DIR}/../backend"
|
||||||
echo " Latest: https://cannaiq.co/downloads/cannaiq-menus-latest.zip"
|
node -e "
|
||||||
|
const Minio = require('minio');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const client = new Minio.Client({
|
||||||
|
endPoint: 'cdn.cannabrands.app',
|
||||||
|
port: 443,
|
||||||
|
useSSL: true,
|
||||||
|
accessKey: 'FLE4dpKrOS2uQM7AFQSY',
|
||||||
|
secretKey: 'wDU6qkruxoDWftIvM0OIdJgTCleTLr5vhozPuYqF',
|
||||||
|
});
|
||||||
|
|
||||||
|
const localFile = 'public/downloads/${OUTPUT_FILE}';
|
||||||
|
const version = '${VERSION}';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Upload versioned file
|
||||||
|
await client.fPutObject('cannaiq', 'downloads/cannaiq-menus-' + version + '.zip', localFile, {
|
||||||
|
'Content-Type': 'application/zip',
|
||||||
|
});
|
||||||
|
console.log(' Uploaded: cannaiq-menus-' + version + '.zip');
|
||||||
|
|
||||||
|
// Upload as latest
|
||||||
|
await client.fPutObject('cannaiq', 'downloads/cannaiq-menus-latest.zip', localFile, {
|
||||||
|
'Content-Type': 'application/zip',
|
||||||
|
});
|
||||||
|
console.log(' Uploaded: cannaiq-menus-latest.zip');
|
||||||
|
})();
|
||||||
|
"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done! Plugin is live at:"
|
||||||
|
echo " https://cdn.cannabrands.app/cannaiq/downloads/cannaiq-menus-${VERSION}.zip"
|
||||||
|
echo " https://cdn.cannabrands.app/cannaiq/downloads/cannaiq-menus-latest.zip"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: CannaIQ Menus
|
* Plugin Name: CannaIQ Menus
|
||||||
* Plugin URI: https://cannaiq.co
|
* Plugin URI: https://cannaiq.co
|
||||||
* Description: Display cannabis product menus from CannaIQ with Elementor integration. Real-time menu data updated daily.
|
* Description: Display cannabis product menus from CannaIQ with Elementor integration. Real-time menu data updated daily.
|
||||||
* Version: 1.6.0
|
* Version: 1.7.0
|
||||||
* Author: CannaIQ
|
* Author: CannaIQ
|
||||||
* Author URI: https://cannaiq.co
|
* Author URI: https://cannaiq.co
|
||||||
* License: GPL v2 or later
|
* License: GPL v2 or later
|
||||||
@@ -15,7 +15,7 @@ if (!defined('ABSPATH')) {
|
|||||||
exit; // Exit if accessed directly
|
exit; // Exit if accessed directly
|
||||||
}
|
}
|
||||||
|
|
||||||
define('CANNAIQ_MENUS_VERSION', '1.6.0');
|
define('CANNAIQ_MENUS_VERSION', '1.7.0');
|
||||||
define('CANNAIQ_MENUS_API_URL', 'https://cannaiq.co/api/v1');
|
define('CANNAIQ_MENUS_API_URL', 'https://cannaiq.co/api/v1');
|
||||||
define('CANNAIQ_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('CANNAIQ_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('CANNAIQ_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('CANNAIQ_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
@@ -36,27 +36,42 @@ class CannaIQ_Menus_Plugin {
|
|||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
add_action('plugins_loaded', [$this, 'init']);
|
add_action('plugins_loaded', [$this, 'init']);
|
||||||
|
add_action('elementor/elements/categories_registered', [$this, 'register_elementor_category']);
|
||||||
add_action('elementor/widgets/register', [$this, 'register_elementor_widgets']);
|
add_action('elementor/widgets/register', [$this, 'register_elementor_widgets']);
|
||||||
add_action('admin_menu', [$this, 'add_admin_menu']);
|
add_action('admin_menu', [$this, 'add_admin_menu']);
|
||||||
add_action('admin_init', [$this, 'register_settings']);
|
add_action('admin_init', [$this, 'register_settings']);
|
||||||
add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
|
add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register CannaIQ Elementor Widget Category
|
||||||
|
*/
|
||||||
|
public function register_elementor_category($elements_manager) {
|
||||||
|
$elements_manager->add_category(
|
||||||
|
'cannaiq',
|
||||||
|
[
|
||||||
|
'title' => __('CannaIQ', 'cannaiq-menus'),
|
||||||
|
'icon' => 'fa fa-cannabis',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
// Initialize plugin
|
// Initialize plugin
|
||||||
load_plugin_textdomain('cannaiq-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
load_plugin_textdomain('cannaiq-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
||||||
|
|
||||||
|
// Load Elementor Dynamic Tags (if Elementor is active)
|
||||||
|
if (did_action('elementor/loaded')) {
|
||||||
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/dynamic-tags.php';
|
||||||
|
}
|
||||||
|
|
||||||
// Register shortcodes - primary CannaIQ shortcodes
|
// Register shortcodes - primary CannaIQ shortcodes
|
||||||
add_shortcode('cannaiq_products', [$this, 'products_shortcode']);
|
add_shortcode('cannaiq_products', [$this, 'products_shortcode']);
|
||||||
add_shortcode('cannaiq_product', [$this, 'single_product_shortcode']);
|
add_shortcode('cannaiq_product', [$this, 'single_product_shortcode']);
|
||||||
|
|
||||||
// DEPRECATED: Legacy shortcode aliases for backward compatibility only
|
// DEPRECATED: Legacy shortcode alias for backward compatibility only
|
||||||
// These allow sites that used the old plugin names to continue working
|
add_shortcode('crawlsy_products', [$this, 'products_shortcode']);
|
||||||
// New implementations should use [cannaiq_products] and [cannaiq_product]
|
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']);
|
||||||
add_shortcode('crawlsy_products', [$this, 'products_shortcode']); // deprecated
|
|
||||||
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']); // deprecated
|
|
||||||
add_shortcode('dutchie_products', [$this, 'products_shortcode']); // deprecated
|
|
||||||
add_shortcode('dutchie_product', [$this, 'single_product_shortcode']); // deprecated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,12 +83,14 @@ class CannaIQ_Menus_Plugin {
|
|||||||
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/brand-grid.php';
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/brand-grid.php';
|
||||||
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/category-list.php';
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/category-list.php';
|
||||||
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/specials-grid.php';
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/specials-grid.php';
|
||||||
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-loop.php';
|
||||||
|
|
||||||
$widgets_manager->register(new \CannaIQ_Menus_Product_Grid_Widget());
|
$widgets_manager->register(new \CannaIQ_Menus_Product_Grid_Widget());
|
||||||
$widgets_manager->register(new \CannaIQ_Menus_Single_Product_Widget());
|
$widgets_manager->register(new \CannaIQ_Menus_Single_Product_Widget());
|
||||||
$widgets_manager->register(new \CannaIQ_Menus_Brand_Grid_Widget());
|
$widgets_manager->register(new \CannaIQ_Menus_Brand_Grid_Widget());
|
||||||
$widgets_manager->register(new \CannaIQ_Menus_Category_List_Widget());
|
$widgets_manager->register(new \CannaIQ_Menus_Category_List_Widget());
|
||||||
$widgets_manager->register(new \CannaIQ_Menus_Specials_Grid_Widget());
|
$widgets_manager->register(new \CannaIQ_Menus_Specials_Grid_Widget());
|
||||||
|
$widgets_manager->register(new \CannaIQ_Product_Loop_Widget());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,18 +134,10 @@ class CannaIQ_Menus_Plugin {
|
|||||||
public function register_settings() {
|
public function register_settings() {
|
||||||
register_setting('cannaiq_menus_settings', 'cannaiq_api_token');
|
register_setting('cannaiq_menus_settings', 'cannaiq_api_token');
|
||||||
|
|
||||||
// MIGRATION: Auto-migrate API tokens from old plugin versions
|
// MIGRATION: Auto-migrate API token from old Crawlsy plugin
|
||||||
// This runs once - if user had crawlsy or dutchie plugin, their token is preserved
|
|
||||||
// Can be removed in a future major version once all users have migrated
|
|
||||||
$old_crawlsy_token = get_option('crawlsy_api_token');
|
$old_crawlsy_token = get_option('crawlsy_api_token');
|
||||||
$old_dutchie_token = get_option('dutchie_api_token');
|
if (!get_option('cannaiq_api_token') && $old_crawlsy_token) {
|
||||||
|
|
||||||
if (!get_option('cannaiq_api_token')) {
|
|
||||||
if ($old_crawlsy_token) {
|
|
||||||
update_option('cannaiq_api_token', $old_crawlsy_token);
|
update_option('cannaiq_api_token', $old_crawlsy_token);
|
||||||
} elseif ($old_dutchie_token) {
|
|
||||||
update_option('cannaiq_api_token', $old_dutchie_token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,12 +46,9 @@ class Crawlsy_Menus_Plugin {
|
|||||||
// Initialize plugin
|
// Initialize plugin
|
||||||
load_plugin_textdomain('crawlsy-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
load_plugin_textdomain('crawlsy-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
||||||
|
|
||||||
// Register shortcodes (keeping backward compatible shortcode names)
|
// Register shortcodes
|
||||||
add_shortcode('crawlsy_products', [$this, 'products_shortcode']);
|
add_shortcode('crawlsy_products', [$this, 'products_shortcode']);
|
||||||
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']);
|
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']);
|
||||||
// Legacy shortcode support
|
|
||||||
add_shortcode('dutchie_products', [$this, 'products_shortcode']);
|
|
||||||
add_shortcode('dutchie_product', [$this, 'single_product_shortcode']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,12 +102,6 @@ class Crawlsy_Menus_Plugin {
|
|||||||
*/
|
*/
|
||||||
public function register_settings() {
|
public function register_settings() {
|
||||||
register_setting('crawlsy_menus_settings', 'crawlsy_api_token');
|
register_setting('crawlsy_menus_settings', 'crawlsy_api_token');
|
||||||
|
|
||||||
// Migrate old setting if exists
|
|
||||||
$old_token = get_option('dutchie_api_token');
|
|
||||||
if ($old_token && !get_option('crawlsy_api_token')) {
|
|
||||||
update_option('crawlsy_api_token', $old_token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
806
wordpress-plugin/widgets/dynamic-tags.php
Normal file
806
wordpress-plugin/widgets/dynamic-tags.php
Normal file
@@ -0,0 +1,806 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CannaIQ Elementor Dynamic Tags
|
||||||
|
*
|
||||||
|
* Registers dynamic tags for all product payload fields.
|
||||||
|
* Users can insert these tags into any Elementor widget.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register CannaIQ dynamic tag group
|
||||||
|
*/
|
||||||
|
add_action('elementor/dynamic_tags/register', function($dynamic_tags_manager) {
|
||||||
|
|
||||||
|
// Register CannaIQ group
|
||||||
|
$dynamic_tags_manager->register_group('cannaiq', [
|
||||||
|
'title' => __('CannaIQ Product', 'cannaiq-menus')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Register all tags
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Product_Name_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Product_Description_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Brand_Name_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Brand_Logo_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Brand_Description_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Category_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Price_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Price_Rec_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Price_Med_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_THC_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_CBD_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Strain_Type_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Effects_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Weight_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Image_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_In_Stock_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_On_Sale_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Sale_Percent_Tag());
|
||||||
|
$dynamic_tags_manager->register(new CannaIQ_Quantity_Tag());
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for CannaIQ dynamic tags
|
||||||
|
*/
|
||||||
|
abstract class CannaIQ_Dynamic_Tag_Base extends \Elementor\Core\DynamicTags\Tag {
|
||||||
|
|
||||||
|
public function get_group() {
|
||||||
|
return 'cannaiq';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current product from context
|
||||||
|
*/
|
||||||
|
protected function get_current_product() {
|
||||||
|
global $cannaiq_current_product;
|
||||||
|
return $cannaiq_current_product ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get nested value from array using dot notation
|
||||||
|
*/
|
||||||
|
protected function get_nested_value($array, $path, $default = '') {
|
||||||
|
$keys = explode('.', $path);
|
||||||
|
$value = $array;
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
// Handle array index like [0]
|
||||||
|
if (preg_match('/^(\w+)\[(\d+)\]$/', $key, $matches)) {
|
||||||
|
$key = $matches[1];
|
||||||
|
$index = (int)$matches[2];
|
||||||
|
if (isset($value[$key][$index])) {
|
||||||
|
$value = $value[$key][$index];
|
||||||
|
} else {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset($value[$key])) {
|
||||||
|
$value = $value[$key];
|
||||||
|
} else {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product Name Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Product_Name_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-product-name';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Product Name', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
echo esc_html($product['Name'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product Description Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Product_Description_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-product-description';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Product Description', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
echo esc_html($product['description'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand Name Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Brand_Name_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-brand-name';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Brand Name', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
echo esc_html($product['brand']['name'] ?? $product['brandName'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand Logo Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Brand_Logo_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-brand-logo';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Brand Logo', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::IMAGE_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$url = $product['brand']['imageUrl'] ?? $product['brandLogo'] ?? '';
|
||||||
|
echo esc_url($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_value(array $options = []) {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$url = $product['brand']['imageUrl'] ?? $product['brandLogo'] ?? '';
|
||||||
|
return [
|
||||||
|
'id' => '',
|
||||||
|
'url' => $url,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand Description Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Brand_Description_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-brand-description';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Brand Description', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
echo esc_html($product['brand']['description'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Category_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-category';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Category', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
echo esc_html($product['type'] ?? $product['category'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Price Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Price_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-price';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Price', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('format', [
|
||||||
|
'label' => __('Format', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::SELECT,
|
||||||
|
'default' => 'currency',
|
||||||
|
'options' => [
|
||||||
|
'currency' => '$XX.XX',
|
||||||
|
'number' => 'XX.XX',
|
||||||
|
'raw' => 'Raw value',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$price = $product['Prices'][0] ?? $product['recPrices'][0] ?? null;
|
||||||
|
$format = $this->get_settings('format');
|
||||||
|
|
||||||
|
if ($price === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'currency':
|
||||||
|
echo '$' . number_format((float)$price, 2);
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
echo number_format((float)$price, 2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo esc_html($price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rec Price Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Price_Rec_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-price-rec';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Rec Price', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$price = $product['recPrices'][0] ?? $product['Prices'][0] ?? null;
|
||||||
|
|
||||||
|
if ($price !== null) {
|
||||||
|
echo '$' . number_format((float)$price, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Med Price Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Price_Med_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-price-med';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Med Price', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$price = $product['medPrices'][0] ?? null;
|
||||||
|
|
||||||
|
// Try POSMetaData
|
||||||
|
if ($price === null && isset($product['POSMetaData']['children'][0]['medPrice'])) {
|
||||||
|
$price = $product['POSMetaData']['children'][0]['medPrice'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($price !== null) {
|
||||||
|
echo '$' . number_format((float)$price, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THC Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_THC_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-thc';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('THC %', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('format', [
|
||||||
|
'label' => __('Format', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::SELECT,
|
||||||
|
'default' => 'percent',
|
||||||
|
'options' => [
|
||||||
|
'percent' => 'XX.X%',
|
||||||
|
'number' => 'XX.X',
|
||||||
|
'badge' => 'THC: XX.X%',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$thc = $product['THCContent']['range'][0] ?? $product['THC'] ?? null;
|
||||||
|
$format = $this->get_settings('format');
|
||||||
|
|
||||||
|
if ($thc === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'percent':
|
||||||
|
echo number_format((float)$thc, 1) . '%';
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
echo number_format((float)$thc, 1);
|
||||||
|
break;
|
||||||
|
case 'badge':
|
||||||
|
echo 'THC: ' . number_format((float)$thc, 1) . '%';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CBD Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_CBD_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-cbd';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('CBD %', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$cbd = $product['CBDContent']['range'][0] ?? $product['CBD'] ?? null;
|
||||||
|
|
||||||
|
if ($cbd !== null) {
|
||||||
|
echo number_format((float)$cbd, 1) . '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strain Type Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Strain_Type_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-strain-type';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Strain Type', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('format', [
|
||||||
|
'label' => __('Format', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::SELECT,
|
||||||
|
'default' => 'full',
|
||||||
|
'options' => [
|
||||||
|
'full' => 'Sativa / Indica / Hybrid',
|
||||||
|
'short' => 'S / I / H',
|
||||||
|
'uppercase' => 'SATIVA / INDICA / HYBRID',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$strain = $product['strainType'] ?? '';
|
||||||
|
$format = $this->get_settings('format');
|
||||||
|
|
||||||
|
if (empty($strain)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'short':
|
||||||
|
echo strtoupper(substr($strain, 0, 1));
|
||||||
|
break;
|
||||||
|
case 'uppercase':
|
||||||
|
echo strtoupper($strain);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo ucfirst($strain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effects Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Effects_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-effects';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Effects', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('format', [
|
||||||
|
'label' => __('Format', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::SELECT,
|
||||||
|
'default' => 'badges',
|
||||||
|
'options' => [
|
||||||
|
'badges' => 'HTML Badges',
|
||||||
|
'list' => 'Comma separated',
|
||||||
|
'top3' => 'Top 3 effects',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->add_control('limit', [
|
||||||
|
'label' => __('Max Effects', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::NUMBER,
|
||||||
|
'default' => 5,
|
||||||
|
'min' => 1,
|
||||||
|
'max' => 10,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$effects = $product['effects'] ?? [];
|
||||||
|
$format = $this->get_settings('format');
|
||||||
|
$limit = (int)$this->get_settings('limit') ?: 5;
|
||||||
|
|
||||||
|
if (empty($effects) || !is_array($effects)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by value descending
|
||||||
|
arsort($effects);
|
||||||
|
|
||||||
|
// Take top N
|
||||||
|
$effects = array_slice($effects, 0, $limit, true);
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'badges':
|
||||||
|
echo '<div class="cannaiq-effects-badges">';
|
||||||
|
foreach ($effects as $effect => $score) {
|
||||||
|
$icon = $this->get_effect_icon($effect);
|
||||||
|
echo '<span class="cannaiq-effect-badge cannaiq-effect-' . esc_attr($effect) . '">';
|
||||||
|
echo $icon . ' ' . esc_html(strtoupper($effect));
|
||||||
|
echo '</span>';
|
||||||
|
}
|
||||||
|
echo '</div>';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
$names = array_map('ucfirst', array_keys($effects));
|
||||||
|
echo esc_html(implode(', ', $names));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'top3':
|
||||||
|
$names = array_slice(array_map('ucfirst', array_keys($effects)), 0, 3);
|
||||||
|
echo esc_html(implode(', ', $names));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_effect_icon($effect) {
|
||||||
|
$icons = [
|
||||||
|
'happy' => '😊',
|
||||||
|
'relaxed' => '😌',
|
||||||
|
'sleepy' => '😴',
|
||||||
|
'focused' => '🎯',
|
||||||
|
'creative' => '🎨',
|
||||||
|
'energetic' => '⚡',
|
||||||
|
'hungry' => '🍽️',
|
||||||
|
'euphoric' => '✨',
|
||||||
|
'uplifted' => '🌟',
|
||||||
|
'talkative' => '💬',
|
||||||
|
'inspired' => '💡',
|
||||||
|
'giggly' => '😄',
|
||||||
|
];
|
||||||
|
return $icons[strtolower($effect)] ?? '•';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weight Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Weight_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-weight';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Weight', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$weight = $product['Options'][0] ?? $product['rawOptions'][0] ?? '';
|
||||||
|
echo esc_html($weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product Image Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Image_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-image';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Product Image', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::IMAGE_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$url = $product['Image'] ?? $product['images'][0]['url'] ?? '';
|
||||||
|
echo esc_url($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_value(array $options = []) {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$url = $product['Image'] ?? $product['images'][0]['url'] ?? '';
|
||||||
|
return [
|
||||||
|
'id' => '',
|
||||||
|
'url' => $url,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In Stock Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_In_Stock_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-in-stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('In Stock', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('in_stock_text', [
|
||||||
|
'label' => __('In Stock Text', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::TEXT,
|
||||||
|
'default' => 'In Stock',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->add_control('out_of_stock_text', [
|
||||||
|
'label' => __('Out of Stock Text', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::TEXT,
|
||||||
|
'default' => 'Out of Stock',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$status = $product['Status'] ?? '';
|
||||||
|
$in_stock = ($status === 'Active' || $status === 'In Stock');
|
||||||
|
|
||||||
|
if ($in_stock) {
|
||||||
|
echo esc_html($this->get_settings('in_stock_text'));
|
||||||
|
} else {
|
||||||
|
echo esc_html($this->get_settings('out_of_stock_text'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Sale Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_On_Sale_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-on-sale';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('On Sale', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('sale_text', [
|
||||||
|
'label' => __('Sale Text', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::TEXT,
|
||||||
|
'default' => 'SALE',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$on_sale = $product['special'] ?? false;
|
||||||
|
|
||||||
|
if ($on_sale) {
|
||||||
|
echo esc_html($this->get_settings('sale_text'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale Percent Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Sale_Percent_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-sale-percent';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Sale Percentage', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register_controls() {
|
||||||
|
$this->add_control('format', [
|
||||||
|
'label' => __('Format', 'cannaiq-menus'),
|
||||||
|
'type' => \Elementor\Controls_Manager::SELECT,
|
||||||
|
'default' => 'badge',
|
||||||
|
'options' => [
|
||||||
|
'badge' => 'XX% OFF',
|
||||||
|
'number' => 'XX',
|
||||||
|
'percent' => 'XX%',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
|
||||||
|
// Calculate discount if we have original and sale price
|
||||||
|
$original = $product['Prices'][0] ?? null;
|
||||||
|
$sale = $product['specialPrice'] ?? null;
|
||||||
|
|
||||||
|
// Check POSMetaData for discount
|
||||||
|
if (isset($product['POSMetaData']['children'][0])) {
|
||||||
|
$child = $product['POSMetaData']['children'][0];
|
||||||
|
if (isset($child['price']) && isset($child['specialPrice'])) {
|
||||||
|
$original = $child['price'];
|
||||||
|
$sale = $child['specialPrice'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($original && $sale && $original > $sale) {
|
||||||
|
$percent = round((($original - $sale) / $original) * 100);
|
||||||
|
$format = $this->get_settings('format');
|
||||||
|
|
||||||
|
switch ($format) {
|
||||||
|
case 'badge':
|
||||||
|
echo $percent . '% OFF';
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
echo $percent;
|
||||||
|
break;
|
||||||
|
case 'percent':
|
||||||
|
echo $percent . '%';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quantity Tag
|
||||||
|
*/
|
||||||
|
class CannaIQ_Quantity_Tag extends CannaIQ_Dynamic_Tag_Base {
|
||||||
|
|
||||||
|
public function get_name() {
|
||||||
|
return 'cannaiq-quantity';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_title() {
|
||||||
|
return __('Stock Quantity', 'cannaiq-menus');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_categories() {
|
||||||
|
return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
$product = $this->get_current_product();
|
||||||
|
$quantity = $product['POSMetaData']['children'][0]['quantity'] ?? null;
|
||||||
|
|
||||||
|
if ($quantity !== null) {
|
||||||
|
echo (int)$quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
858
wordpress-plugin/widgets/product-loop.php
Normal file
858
wordpress-plugin/widgets/product-loop.php
Normal file
@@ -0,0 +1,858 @@
|
|||||||
|
<?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'] ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user