Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Add 14 new shortcodes for building custom product cards - Add visual builder guide with Hot Lava example in admin page - Add comprehensive component documentation - Update branding to "CannaiQ" throughout - Include layout shortcodes: specials, brands, categories - Include component shortcodes: discount_badge, strain_badge, thc, cbd, effects, price, cart_button, stock, terpenes - Add build steps and instructions for assembling cards 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1094 lines
46 KiB
PHP
1094 lines
46 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: CannaiQ Menus
|
|
* Plugin URI: https://cannaiq.co
|
|
* Description: Display cannabis product menus from CannaiQ with Elementor integration. Real-time menu data updated daily.
|
|
* Version: 2.0.0
|
|
* Author: CannaiQ
|
|
* Author URI: https://cannaiq.co
|
|
* License: GPL v2 or later
|
|
* Text Domain: cannaiq-menus
|
|
* Requires PHP: 7.4
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit; // Exit if accessed directly
|
|
}
|
|
|
|
define('CANNAIQ_MENUS_VERSION', '2.0.0');
|
|
define('CANNAIQ_MENUS_API_URL', 'https://cannaiq.co/api/v1');
|
|
define('CANNAIQ_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
define('CANNAIQ_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
|
|
/**
|
|
* Main Plugin Class
|
|
*/
|
|
class CannaIQ_Menus_Plugin {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function __construct() {
|
|
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('admin_menu', [$this, 'add_admin_menu']);
|
|
add_action('admin_init', [$this, 'register_settings']);
|
|
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() {
|
|
// Initialize plugin
|
|
load_plugin_textdomain('cannaiq-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
|
|
|
// Load helper functions
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'includes/effects-icons.php';
|
|
|
|
// Load Elementor Dynamic Tags (if Elementor is active)
|
|
if (did_action('elementor/loaded')) {
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/dynamic-tags.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/dynamic-tags-extended.php';
|
|
}
|
|
|
|
// Register shortcodes - primary CannaiQ shortcodes
|
|
add_shortcode('cannaiq_products', [$this, 'products_shortcode']);
|
|
add_shortcode('cannaiq_product', [$this, 'single_product_shortcode']);
|
|
add_shortcode('cannaiq_specials', [$this, 'specials_shortcode']);
|
|
add_shortcode('cannaiq_brands', [$this, 'brands_shortcode']);
|
|
add_shortcode('cannaiq_categories', [$this, 'categories_shortcode']);
|
|
|
|
// Component shortcodes (v2.0)
|
|
add_shortcode('cannaiq_discount_badge', [$this, 'discount_badge_shortcode']);
|
|
add_shortcode('cannaiq_strain_badge', [$this, 'strain_badge_shortcode']);
|
|
add_shortcode('cannaiq_thc', [$this, 'thc_shortcode']);
|
|
add_shortcode('cannaiq_cbd', [$this, 'cbd_shortcode']);
|
|
add_shortcode('cannaiq_effects', [$this, 'effects_shortcode']);
|
|
add_shortcode('cannaiq_price', [$this, 'price_shortcode']);
|
|
add_shortcode('cannaiq_cart_button', [$this, 'cart_button_shortcode']);
|
|
add_shortcode('cannaiq_stock', [$this, 'stock_shortcode']);
|
|
add_shortcode('cannaiq_terpenes', [$this, 'terpenes_shortcode']);
|
|
|
|
// DEPRECATED: Legacy shortcode alias for backward compatibility only
|
|
add_shortcode('crawlsy_products', [$this, 'products_shortcode']);
|
|
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']);
|
|
}
|
|
|
|
/**
|
|
* Register Elementor Widgets
|
|
*/
|
|
public function register_elementor_widgets($widgets_manager) {
|
|
// Legacy widgets
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-grid.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/single-product.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/specials-grid.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-loop.php';
|
|
|
|
// Modular component widgets (v2.0)
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/discount-ribbon.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/strain-badge.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/thc-meter.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/effects-display.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/price-block.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/cart-button.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/stock-indicator.php';
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-image-overlay.php';
|
|
|
|
// Card templates (v2.0)
|
|
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/card-template-premium.php';
|
|
|
|
// Register legacy widgets
|
|
$widgets_manager->register(new \CannaIQ_Menus_Product_Grid_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_Category_List_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Menus_Specials_Grid_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Product_Loop_Widget());
|
|
|
|
// Register modular component widgets (v2.0)
|
|
$widgets_manager->register(new \CannaIQ_Discount_Ribbon_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Strain_Badge_Widget());
|
|
$widgets_manager->register(new \CannaIQ_THC_Meter_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Effects_Display_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Price_Block_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Cart_Button_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Stock_Indicator_Widget());
|
|
$widgets_manager->register(new \CannaIQ_Product_Image_Overlay_Widget());
|
|
|
|
// Register card templates (v2.0)
|
|
$widgets_manager->register(new \CannaIQ_Premium_Card_Widget());
|
|
}
|
|
|
|
/**
|
|
* Enqueue Scripts and Styles
|
|
*/
|
|
public function enqueue_scripts() {
|
|
// Base styles
|
|
wp_enqueue_style(
|
|
'cannaiq-menus-styles',
|
|
CANNAIQ_MENUS_PLUGIN_URL . 'assets/css/cannaiq-menus.css',
|
|
[],
|
|
CANNAIQ_MENUS_VERSION
|
|
);
|
|
|
|
// Component styles (v2.0 modular components)
|
|
wp_enqueue_style(
|
|
'cannaiq-components-styles',
|
|
CANNAIQ_MENUS_PLUGIN_URL . 'assets/css/components.css',
|
|
['cannaiq-menus-styles'],
|
|
CANNAIQ_MENUS_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'cannaiq-menus-script',
|
|
CANNAIQ_MENUS_PLUGIN_URL . 'assets/js/cannaiq-menus.js',
|
|
['jquery'],
|
|
CANNAIQ_MENUS_VERSION,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add Admin Menu
|
|
*/
|
|
public function add_admin_menu() {
|
|
add_menu_page(
|
|
'CannaiQ Menus',
|
|
'CannaiQ Menus',
|
|
'manage_options',
|
|
'cannaiq-menus',
|
|
[$this, 'admin_page'],
|
|
'dashicons-products',
|
|
30
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register Plugin Settings
|
|
*/
|
|
public function register_settings() {
|
|
register_setting('cannaiq_menus_settings', 'cannaiq_api_token');
|
|
|
|
// MIGRATION: Auto-migrate API token from old Crawlsy plugin
|
|
$old_crawlsy_token = get_option('crawlsy_api_token');
|
|
if (!get_option('cannaiq_api_token') && $old_crawlsy_token) {
|
|
update_option('cannaiq_api_token', $old_crawlsy_token);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Admin Page
|
|
*/
|
|
public function admin_page() {
|
|
?>
|
|
<div class="wrap">
|
|
<h1>CannaiQ Menus Settings</h1>
|
|
<p>Version <?php echo CANNAIQ_MENUS_VERSION; ?> by <a href="https://cannaiq.co" target="_blank">CannaiQ</a></p>
|
|
<p class="description">Display real-time cannabis menus with data updated daily from CannaiQ.</p>
|
|
|
|
<form method="post" action="options.php">
|
|
<?php settings_fields('cannaiq_menus_settings'); ?>
|
|
<?php do_settings_sections('cannaiq_menus_settings'); ?>
|
|
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row"><label for="cannaiq_api_token">API Token</label></th>
|
|
<td>
|
|
<input type="password" id="cannaiq_api_token" name="cannaiq_api_token"
|
|
value="<?php echo esc_attr(get_option('cannaiq_api_token')); ?>"
|
|
class="regular-text" />
|
|
<p class="description">Your authentication token from the CannaiQ admin dashboard. The token includes your store configuration.</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php submit_button(); ?>
|
|
</form>
|
|
|
|
<hr />
|
|
|
|
<h2>Test Connection</h2>
|
|
<button type="button" id="test-api-connection" class="button button-primary">Test API Connection</button>
|
|
<div id="api-test-result" style="margin-top: 10px;"></div>
|
|
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
$('#test-api-connection').on('click', function() {
|
|
var apiUrl = '<?php echo CANNAIQ_MENUS_API_URL; ?>';
|
|
var apiToken = $('#cannaiq_api_token').val();
|
|
|
|
if (!apiToken || apiToken.trim() === '') {
|
|
$('#api-test-result').html(
|
|
'<div class="notice notice-warning"><p><strong>Warning:</strong> Please enter an API token first.</p></div>'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$('#api-test-result').html('<p><span class="spinner is-active" style="float:none;margin:0 5px 0 0;"></span>Testing connection...</p>');
|
|
|
|
$.ajax({
|
|
url: apiUrl + '/menu',
|
|
method: 'GET',
|
|
headers: {
|
|
'X-API-Key': apiToken
|
|
},
|
|
timeout: 30000,
|
|
success: function(response) {
|
|
var html = '<div class="notice notice-success"><p><strong>Success!</strong> Connected to: ' +
|
|
response.dispensary + '</p>';
|
|
if (response.menu) {
|
|
html += '<ul style="margin-left: 20px;">';
|
|
html += '<li>Total Products: ' + (response.menu.total_products || 0) + '</li>';
|
|
html += '<li>In Stock: ' + (response.menu.in_stock_count || 0) + '</li>';
|
|
html += '<li>Brands: ' + (response.menu.brand_count || 0) + '</li>';
|
|
html += '<li>Categories: ' + (response.menu.category_count || 0) + '</li>';
|
|
if (response.menu.last_updated) {
|
|
html += '<li>Last Updated: ' + new Date(response.menu.last_updated).toLocaleString() + '</li>';
|
|
}
|
|
html += '</ul>';
|
|
}
|
|
html += '</div>';
|
|
$('#api-test-result').html(html);
|
|
},
|
|
error: function(xhr, textStatus, errorThrown) {
|
|
var errorHtml = '<div class="notice notice-error">';
|
|
errorHtml += '<p><strong>Connection Failed</strong></p>';
|
|
|
|
// Show HTTP status
|
|
if (xhr.status) {
|
|
errorHtml += '<p><strong>HTTP Status:</strong> ' + xhr.status + ' ' + (xhr.statusText || '') + '</p>';
|
|
}
|
|
|
|
// Show error message from API
|
|
if (xhr.responseJSON) {
|
|
if (xhr.responseJSON.error) {
|
|
errorHtml += '<p><strong>Error:</strong> ' + xhr.responseJSON.error + '</p>';
|
|
}
|
|
if (xhr.responseJSON.message) {
|
|
errorHtml += '<p><strong>Details:</strong> ' + xhr.responseJSON.message + '</p>';
|
|
}
|
|
if (xhr.responseJSON.client_ip) {
|
|
errorHtml += '<p><strong>Your IP:</strong> ' + xhr.responseJSON.client_ip + '</p>';
|
|
}
|
|
if (xhr.responseJSON.origin) {
|
|
errorHtml += '<p><strong>Origin:</strong> ' + xhr.responseJSON.origin + '</p>';
|
|
}
|
|
} else if (textStatus === 'timeout') {
|
|
errorHtml += '<p><strong>Error:</strong> Request timed out. The API server may be unavailable.</p>';
|
|
} else if (textStatus === 'error' && !xhr.status) {
|
|
errorHtml += '<p><strong>Error:</strong> Could not connect to the API. This may be a CORS issue or network problem.</p>';
|
|
errorHtml += '<p><em>Note: If testing from wp-admin, try saving the token and testing on the frontend shortcode instead.</em></p>';
|
|
} else {
|
|
errorHtml += '<p><strong>Error:</strong> ' + (errorThrown || textStatus || 'Unknown error') + '</p>';
|
|
}
|
|
|
|
// Debug info
|
|
errorHtml += '<details style="margin-top: 10px;"><summary style="cursor:pointer;"><strong>Debug Info</strong></summary>';
|
|
errorHtml += '<pre style="background:#f5f5f5;padding:10px;margin-top:5px;overflow-x:auto;font-size:11px;">';
|
|
errorHtml += 'API URL: ' + apiUrl + '/menu\n';
|
|
errorHtml += 'Token Length: ' + apiToken.length + ' chars\n';
|
|
errorHtml += 'Token Preview: ' + apiToken.substring(0, 8) + '...' + apiToken.substring(apiToken.length - 4) + '\n';
|
|
errorHtml += 'Status: ' + xhr.status + '\n';
|
|
errorHtml += 'Response: ' + (xhr.responseText ? xhr.responseText.substring(0, 500) : 'none') + '\n';
|
|
errorHtml += '</pre></details>';
|
|
|
|
// Help text based on status
|
|
if (xhr.status === 401) {
|
|
errorHtml += '<p style="margin-top:10px;"><strong>Troubleshooting:</strong> Your API token is invalid or inactive. Please check that you copied the full token from the CannaIQ admin dashboard.</p>';
|
|
} else if (xhr.status === 403) {
|
|
errorHtml += '<p style="margin-top:10px;"><strong>Troubleshooting:</strong> Access denied. Your IP address or domain may not be in the allowed list. Contact support to update your permissions.</p>';
|
|
} else if (xhr.status === 500) {
|
|
errorHtml += '<p style="margin-top:10px;"><strong>Troubleshooting:</strong> Server error. This may be a temporary issue. Try again in a few minutes, or contact support if the problem persists.</p>';
|
|
} else if (xhr.status === 503) {
|
|
errorHtml += '<p style="margin-top:10px;"><strong>Troubleshooting:</strong> Menu data is not yet available for your dispensary. The data may still be processing.</p>';
|
|
}
|
|
|
|
errorHtml += '</div>';
|
|
$('#api-test-result').html(errorHtml);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<hr />
|
|
|
|
<h2>Usage</h2>
|
|
|
|
<h3>Shortcodes</h3>
|
|
<table class="widefat" style="max-width: 900px;">
|
|
<thead>
|
|
<tr>
|
|
<th>Shortcode</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>[cannaiq_products]</code></td>
|
|
<td>Product grid. Options: <code>category</code>, <code>brand</code>, <code>limit</code>, <code>columns</code>, <code>in_stock</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_product id="123"]</code></td>
|
|
<td>Single product by ID</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_specials]</code></td>
|
|
<td>Products on sale. Options: <code>limit</code>, <code>columns</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_brands]</code></td>
|
|
<td>Brand grid. Options: <code>limit</code>, <code>columns</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_categories]</code></td>
|
|
<td>Category list. Options: <code>style</code> (list|grid)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h4 style="margin-top: 20px;">Component Shortcodes <span style="color: #666; font-weight: normal;">(use inside product context)</span></h4>
|
|
<table class="widefat" style="max-width: 900px;">
|
|
<thead>
|
|
<tr>
|
|
<th>Shortcode</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>[cannaiq_discount_badge]</code></td>
|
|
<td>Discount ribbon/pill. Options: <code>style</code> (ribbon|pill|text)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_strain_badge]</code></td>
|
|
<td>Sativa/Indica/Hybrid badge. Options: <code>style</code> (pill|text)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_thc]</code></td>
|
|
<td>THC percentage. Options: <code>style</code> (meter|badge|pill|text)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_cbd]</code></td>
|
|
<td>CBD percentage. Options: <code>style</code> (meter|badge|pill|text)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_effects]</code></td>
|
|
<td>Effect chips with icons. Options: <code>limit</code>, <code>icons</code> (yes|no)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_price]</code></td>
|
|
<td>Price display. Options: <code>show_original</code> (yes|no), <code>show_weight</code> (yes|no)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_cart_button]</code></td>
|
|
<td>Add to cart button. Options: <code>text</code>, <code>style</code> (solid|outline)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_stock]</code></td>
|
|
<td>Stock status. Options: <code>style</code> (badge|text|dot)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>[cannaiq_terpenes]</code></td>
|
|
<td>Terpene profile. Options: <code>limit</code>, <code>style</code> (chips|list|text)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3 style="margin-top: 30px;">Elementor Widgets</h3>
|
|
<p>With Elementor installed, find CannaiQ widgets in the editor:</p>
|
|
|
|
<h4>Layout Widgets</h4>
|
|
<ul style="list-style: disc; margin-left: 20px;">
|
|
<li><strong>Product Grid</strong> - Filterable product grid</li>
|
|
<li><strong>Product Loop</strong> - Custom loop for building cards</li>
|
|
<li><strong>Single Product</strong> - Display one product</li>
|
|
<li><strong>Brand Grid</strong> - Display brands</li>
|
|
<li><strong>Category List</strong> - Display categories</li>
|
|
<li><strong>Specials/Deals</strong> - Products on sale</li>
|
|
</ul>
|
|
|
|
<h4>Component Widgets <span style="color: #666; font-weight: normal;">(v2.0)</span></h4>
|
|
<ul style="list-style: disc; margin-left: 20px;">
|
|
<li><strong>Discount Ribbon</strong> - Sale percentage badge</li>
|
|
<li><strong>Strain Badge</strong> - Sativa/Indica/Hybrid pill</li>
|
|
<li><strong>THC/CBD Meter</strong> - Potency display</li>
|
|
<li><strong>Effects Display</strong> - Effect chips with icons</li>
|
|
<li><strong>Price Block</strong> - Price with sale formatting</li>
|
|
<li><strong>Cart Button</strong> - Styled CTA button</li>
|
|
<li><strong>Stock Indicator</strong> - Availability badge</li>
|
|
<li><strong>Product Image + Badges</strong> - Image with overlays</li>
|
|
</ul>
|
|
|
|
<h4>Card Templates <span style="color: #666; font-weight: normal;">(v2.0)</span></h4>
|
|
<ul style="list-style: disc; margin-left: 20px;">
|
|
<li><strong>Premium Product Card</strong> - Ready-to-use card with all components</li>
|
|
</ul>
|
|
|
|
<h3 style="margin-top: 30px;">Dynamic Tags</h3>
|
|
<p>In Elementor, use dynamic tags to insert product data into any widget. Look for "CannaiQ Product" in the dynamic tags menu.</p>
|
|
|
|
<hr style="margin: 30px 0;" />
|
|
|
|
<h2>How to Build a Product Card</h2>
|
|
<p>Use the modular components to build custom product cards. Here's an example layout:</p>
|
|
|
|
<div style="display: flex; gap: 30px; flex-wrap: wrap; margin-top: 20px;">
|
|
<!-- Visual Example -->
|
|
<div style="background: #fff; border: 2px solid #ddd; border-radius: 12px; width: 300px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
|
|
<!-- Image area -->
|
|
<div style="background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%); height: 200px; position: relative;">
|
|
<span style="position: absolute; top: 0; left: 0; background: #ef4444; color: white; padding: 4px 12px; font-size: 12px; font-weight: bold; border-bottom-right-radius: 8px;">67% OFF</span>
|
|
<div style="position: absolute; bottom: 8px; left: 8px; display: flex; gap: 4px;">
|
|
<span style="background: #22c55e; color: white; padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight: bold;">SATIVA</span>
|
|
<span style="background: #1f2937; color: white; padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight: bold;">24.5% THC</span>
|
|
</div>
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #999; font-size: 48px;">🌿</div>
|
|
</div>
|
|
<!-- Body -->
|
|
<div style="padding: 16px;">
|
|
<h4 style="margin: 0 0 4px 0; font-size: 18px;">Hot Lava</h4>
|
|
<p style="margin: 0 0 12px 0; color: #666; font-size: 14px;">by TruInfusion</p>
|
|
<div style="display: flex; gap: 6px; margin-bottom: 12px;">
|
|
<span style="background: #fef3c7; border: 1px solid #fcd34d; padding: 2px 8px; border-radius: 999px; font-size: 11px;">😴 Sleepy</span>
|
|
<span style="background: #dbeafe; border: 1px solid #93c5fd; padding: 2px 8px; border-radius: 999px; font-size: 11px;">😌 Relaxed</span>
|
|
<span style="background: #fce7f3; border: 1px solid #f9a8d4; padding: 2px 8px; border-radius: 999px; font-size: 11px;">😊 Happy</span>
|
|
</div>
|
|
<div style="margin-bottom: 12px;">
|
|
<span style="color: #999; font-size: 14px;">1/8 oz</span>
|
|
<span style="color: #999; text-decoration: line-through; margin-left: 8px;">$45.00</span>
|
|
<span style="color: #dc2626; font-weight: bold; font-size: 18px; margin-left: 8px;">$15.00</span>
|
|
</div>
|
|
<div style="background: #1f2937; color: white; text-align: center; padding: 10px; border-radius: 6px; font-weight: bold; font-size: 14px;">ADD TO CART →</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Component Labels -->
|
|
<div style="flex: 1; min-width: 300px;">
|
|
<h4 style="margin-top: 0;">Components Used:</h4>
|
|
<table class="widefat" style="max-width: 400px;">
|
|
<tr><td style="width: 40%;"><strong>Discount Ribbon</strong></td><td>Top-left corner badge</td></tr>
|
|
<tr><td><strong>Product Image</strong></td><td>With badge overlays</td></tr>
|
|
<tr><td><strong>Strain Badge</strong></td><td>Green Sativa pill</td></tr>
|
|
<tr><td><strong>THC Badge</strong></td><td>Dark potency pill</td></tr>
|
|
<tr><td><strong>Product Name</strong></td><td>Dynamic tag</td></tr>
|
|
<tr><td><strong>Brand Name</strong></td><td>Dynamic tag</td></tr>
|
|
<tr><td><strong>Effects Display</strong></td><td>Colored chips with icons</td></tr>
|
|
<tr><td><strong>Price Block</strong></td><td>Weight + strikethrough + sale</td></tr>
|
|
<tr><td><strong>Cart Button</strong></td><td>Links to dispensary menu</td></tr>
|
|
</table>
|
|
|
|
<h4 style="margin-top: 20px;">Build Steps:</h4>
|
|
<ol style="margin-left: 20px; line-height: 1.8;">
|
|
<li>Add a <strong>Product Loop</strong> widget</li>
|
|
<li>Inside the loop, add a container</li>
|
|
<li>Add <strong>Product Image + Badges</strong> widget</li>
|
|
<li>Add heading with <strong>Product Name</strong> dynamic tag</li>
|
|
<li>Add text with <strong>Brand Name</strong> dynamic tag</li>
|
|
<li>Add <strong>Effects Display</strong> widget</li>
|
|
<li>Add <strong>Price Block</strong> widget</li>
|
|
<li>Add <strong>Cart Button</strong> widget</li>
|
|
</ol>
|
|
|
|
<p style="margin-top: 20px; padding: 12px; background: #e7f3ff; border-left: 4px solid #2196f3; border-radius: 4px;">
|
|
<strong>Tip:</strong> Use the <strong>Premium Product Card</strong> template widget for a ready-to-use version of this layout!
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Products Shortcode
|
|
*/
|
|
public function products_shortcode($atts) {
|
|
$atts = shortcode_atts([
|
|
'category_id' => '',
|
|
'limit' => 12,
|
|
'columns' => 3,
|
|
'in_stock' => 'true'
|
|
], $atts);
|
|
|
|
$products = $this->fetch_products($atts);
|
|
|
|
if (!$products) {
|
|
return '<p>No products found.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
include CANNAIQ_MENUS_PLUGIN_DIR . 'templates/product-grid.php';
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Single Product Shortcode
|
|
*/
|
|
public function single_product_shortcode($atts) {
|
|
$atts = shortcode_atts([
|
|
'id' => 0
|
|
], $atts);
|
|
|
|
if (!$atts['id']) {
|
|
return '<p>Product ID required.</p>';
|
|
}
|
|
|
|
$product = $this->fetch_product($atts['id']);
|
|
|
|
if (!$product) {
|
|
return '<p>Product not found.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
include CANNAIQ_MENUS_PLUGIN_DIR . 'templates/single-product.php';
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Specials Shortcode
|
|
*/
|
|
public function specials_shortcode($atts) {
|
|
$atts = shortcode_atts([
|
|
'limit' => 12,
|
|
'columns' => 3
|
|
], $atts);
|
|
|
|
$products = $this->fetch_specials($atts);
|
|
|
|
if (!$products) {
|
|
return '<p>No specials found.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
include CANNAIQ_MENUS_PLUGIN_DIR . 'templates/product-grid.php';
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Brands Shortcode
|
|
*/
|
|
public function brands_shortcode($atts) {
|
|
$atts = shortcode_atts([
|
|
'limit' => 20,
|
|
'columns' => 4
|
|
], $atts);
|
|
|
|
$brands = $this->fetch_brands($atts);
|
|
|
|
if (!$brands) {
|
|
return '<p>No brands found.</p>';
|
|
}
|
|
|
|
$columns = intval($atts['columns']);
|
|
ob_start();
|
|
?>
|
|
<div class="cannaiq-brands-grid" style="display: grid; grid-template-columns: repeat(<?php echo $columns; ?>, 1fr); gap: 20px;">
|
|
<?php foreach ($brands as $brand): ?>
|
|
<div class="cannaiq-brand-card" style="text-align: center; padding: 20px; background: #f9fafb; border-radius: 8px;">
|
|
<?php if (!empty($brand['logo'])): ?>
|
|
<img src="<?php echo esc_url($brand['logo']); ?>" alt="<?php echo esc_attr($brand['brand'] ?? $brand['name']); ?>" style="max-height: 60px; margin-bottom: 10px;" />
|
|
<?php endif; ?>
|
|
<h4 style="margin: 0;"><?php echo esc_html($brand['brand'] ?? $brand['name']); ?></h4>
|
|
<?php if (!empty($brand['product_count'])): ?>
|
|
<p style="margin: 5px 0 0; color: #666; font-size: 14px;"><?php echo intval($brand['product_count']); ?> products</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Categories Shortcode
|
|
*/
|
|
public function categories_shortcode($atts) {
|
|
$atts = shortcode_atts([
|
|
'style' => 'list'
|
|
], $atts);
|
|
|
|
$categories = $this->fetch_categories();
|
|
|
|
if (!$categories) {
|
|
return '<p>No categories found.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
if ($atts['style'] === 'grid') {
|
|
?>
|
|
<div class="cannaiq-categories-grid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;">
|
|
<?php foreach ($categories as $cat): ?>
|
|
<div class="cannaiq-category-card" style="padding: 15px; background: #f9fafb; border-radius: 8px; text-align: center;">
|
|
<h4 style="margin: 0;"><?php echo esc_html(ucwords(str_replace('_', ' ', $cat['type'] ?? $cat['name']))); ?></h4>
|
|
<?php if (!empty($cat['count'])): ?>
|
|
<p style="margin: 5px 0 0; color: #666; font-size: 14px;"><?php echo intval($cat['count']); ?> products</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php
|
|
} else {
|
|
?>
|
|
<ul class="cannaiq-categories-list" style="list-style: none; padding: 0; margin: 0;">
|
|
<?php foreach ($categories as $cat): ?>
|
|
<li style="padding: 10px 0; border-bottom: 1px solid #eee;">
|
|
<?php echo esc_html(ucwords(str_replace('_', ' ', $cat['type'] ?? $cat['name']))); ?>
|
|
<?php if (!empty($cat['count'])): ?>
|
|
<span style="color: #666; font-size: 14px;">(<?php echo intval($cat['count']); ?>)</span>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php
|
|
}
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Discount Badge Shortcode
|
|
*/
|
|
public function discount_badge_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['style' => 'ribbon'], $atts);
|
|
$product = $cannaiq_current_product;
|
|
|
|
$original = $product['Prices'][0] ?? $product['regular_price'] ?? null;
|
|
$sale = $product['specialPrice'] ?? $product['sale_price'] ?? null;
|
|
|
|
if (!$original || !$sale || $original <= $sale) return '';
|
|
|
|
$percent = round((($original - $sale) / $original) * 100);
|
|
$class = 'cannaiq-discount-ribbon cannaiq-discount-ribbon--' . esc_attr($atts['style']);
|
|
|
|
return sprintf('<span class="%s">%s%% OFF</span>', $class, $percent);
|
|
}
|
|
|
|
/**
|
|
* Strain Badge Shortcode
|
|
*/
|
|
public function strain_badge_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['style' => 'pill'], $atts);
|
|
$strain = strtolower($cannaiq_current_product['strainType'] ?? $cannaiq_current_product['strain_type'] ?? '');
|
|
|
|
if (empty($strain) || !in_array($strain, ['sativa', 'indica', 'hybrid'])) return '';
|
|
|
|
$colors = ['sativa' => '#22c55e', 'indica' => '#8b5cf6', 'hybrid' => '#f97316'];
|
|
$color = $colors[$strain];
|
|
$style = $atts['style'] === 'pill' ? "background-color: {$color}; color: white;" : "color: {$color};";
|
|
|
|
return sprintf('<span class="cannaiq-strain-badge cannaiq-strain-badge--%s" style="%s">%s</span>',
|
|
esc_attr($atts['style']), esc_attr($style), esc_html(strtoupper($strain)));
|
|
}
|
|
|
|
/**
|
|
* THC Shortcode
|
|
*/
|
|
public function thc_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['style' => 'badge'], $atts);
|
|
$thc = $cannaiq_current_product['THCContent']['range'][0] ?? $cannaiq_current_product['THC'] ?? $cannaiq_current_product['thc_percentage'] ?? null;
|
|
|
|
if (!$thc || $thc <= 0) return '';
|
|
|
|
$formatted = number_format((float)$thc, 1) . '% THC';
|
|
return sprintf('<span class="cannaiq-potency-badge cannaiq-potency-badge--%s">%s</span>',
|
|
esc_attr($atts['style']), esc_html($formatted));
|
|
}
|
|
|
|
/**
|
|
* CBD Shortcode
|
|
*/
|
|
public function cbd_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['style' => 'badge'], $atts);
|
|
$cbd = $cannaiq_current_product['CBDContent']['range'][0] ?? $cannaiq_current_product['CBD'] ?? $cannaiq_current_product['cbd_percentage'] ?? null;
|
|
|
|
if (!$cbd || $cbd <= 0) return '';
|
|
|
|
$formatted = number_format((float)$cbd, 1) . '% CBD';
|
|
return sprintf('<span class="cannaiq-potency-badge cannaiq-potency-badge--%s">%s</span>',
|
|
esc_attr($atts['style']), esc_html($formatted));
|
|
}
|
|
|
|
/**
|
|
* Effects Shortcode
|
|
*/
|
|
public function effects_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['limit' => 3, 'icons' => 'yes'], $atts);
|
|
$effects = $cannaiq_current_product['effects'] ?? [];
|
|
|
|
if (empty($effects) || !is_array($effects)) return '';
|
|
|
|
if (!isset($effects[0])) {
|
|
arsort($effects);
|
|
$effects = array_keys($effects);
|
|
}
|
|
|
|
$effects = array_slice($effects, 0, intval($atts['limit']));
|
|
return cannaiq_render_effects($effects, [
|
|
'limit' => intval($atts['limit']),
|
|
'show_icon' => $atts['icons'] === 'yes',
|
|
'size' => 'medium'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Price Shortcode
|
|
*/
|
|
public function price_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['show_original' => 'yes', 'show_weight' => 'yes'], $atts);
|
|
$product = $cannaiq_current_product;
|
|
|
|
$original = $product['Prices'][0] ?? $product['regular_price'] ?? null;
|
|
$sale = $product['specialPrice'] ?? $product['sale_price'] ?? null;
|
|
$weight = $product['Options'][0] ?? $product['weight'] ?? '';
|
|
|
|
if (!$original || $original <= 0) return '';
|
|
|
|
$is_sale = $sale && $sale > 0 && $sale < $original;
|
|
ob_start();
|
|
?>
|
|
<span class="cannaiq-price-block">
|
|
<?php if ($atts['show_weight'] === 'yes' && !empty($weight)): ?>
|
|
<span class="cannaiq-price-block__weight"><?php echo esc_html($weight); ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($is_sale): ?>
|
|
<?php if ($atts['show_original'] === 'yes'): ?>
|
|
<span class="cannaiq-price-block__original">$<?php echo number_format((float)$original, 2); ?></span>
|
|
<?php endif; ?>
|
|
<span class="cannaiq-price-block__sale">$<?php echo number_format((float)$sale, 2); ?></span>
|
|
<?php else: ?>
|
|
<span class="cannaiq-price-block__regular">$<?php echo number_format((float)$original, 2); ?></span>
|
|
<?php endif; ?>
|
|
</span>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Cart Button Shortcode
|
|
*/
|
|
public function cart_button_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['text' => 'ADD TO CART', 'style' => 'solid'], $atts);
|
|
$url = $cannaiq_current_product['menuUrl'] ?? $cannaiq_current_product['menu_url'] ?? '#';
|
|
|
|
return sprintf('<a href="%s" class="cannaiq-cart-button cannaiq-cart-button--%s" target="_blank" rel="noopener">%s</a>',
|
|
esc_url($url), esc_attr($atts['style']), esc_html($atts['text']));
|
|
}
|
|
|
|
/**
|
|
* Stock Shortcode
|
|
*/
|
|
public function stock_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['style' => 'badge'], $atts);
|
|
$status = $cannaiq_current_product['Status'] ?? '';
|
|
$in_stock = ($status === 'Active' || $status === 'In Stock' || !empty($cannaiq_current_product['in_stock']));
|
|
|
|
$text = $in_stock ? 'In Stock' : 'Out of Stock';
|
|
$class = 'cannaiq-stock-indicator cannaiq-stock-indicator--' . ($in_stock ? 'in-stock' : 'out-of-stock');
|
|
if ($atts['style'] === 'badge') $class .= ' cannaiq-stock-indicator--badge';
|
|
|
|
$dot = $atts['style'] === 'dot' ? '<span class="cannaiq-stock-indicator__dot"></span>' : '';
|
|
return sprintf('<span class="%s">%s%s</span>', esc_attr($class), $dot, esc_html($text));
|
|
}
|
|
|
|
/**
|
|
* Terpenes Shortcode
|
|
*/
|
|
public function terpenes_shortcode($atts) {
|
|
global $cannaiq_current_product;
|
|
if (!$cannaiq_current_product) return '';
|
|
|
|
$atts = shortcode_atts(['limit' => 3, 'style' => 'chips'], $atts);
|
|
$terpenes = $cannaiq_current_product['terpenes'] ?? [];
|
|
|
|
if (empty($terpenes) || !is_array($terpenes)) return '';
|
|
|
|
$terpenes = array_slice($terpenes, 0, intval($atts['limit']));
|
|
ob_start();
|
|
|
|
if ($atts['style'] === 'chips') {
|
|
echo '<div class="cannaiq-terpenes cannaiq-terpenes--chips">';
|
|
foreach ($terpenes as $terp) {
|
|
$name = $terp['name'] ?? '';
|
|
$percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : '';
|
|
printf('<span class="cannaiq-terpene-chip"><span class="cannaiq-terpene-chip__name">%s</span><span class="cannaiq-terpene-chip__percent">%s</span></span>',
|
|
esc_html($name), esc_html($percent));
|
|
}
|
|
echo '</div>';
|
|
} elseif ($atts['style'] === 'text') {
|
|
$parts = [];
|
|
foreach ($terpenes as $terp) {
|
|
$name = $terp['name'] ?? '';
|
|
$percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : '';
|
|
$parts[] = $name . ($percent ? ' ' . $percent : '');
|
|
}
|
|
echo esc_html(implode(', ', $parts));
|
|
} else {
|
|
echo '<div class="cannaiq-terpenes cannaiq-terpenes--list">';
|
|
foreach ($terpenes as $terp) {
|
|
$name = $terp['name'] ?? '';
|
|
$percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : '';
|
|
printf('<div class="cannaiq-terpene-item"><span>%s</span><span>%s</span></div>',
|
|
esc_html($name), esc_html($percent));
|
|
}
|
|
echo '</div>';
|
|
}
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Fetch Products from API
|
|
*/
|
|
public function fetch_products($args = []) {
|
|
$api_token = get_option('cannaiq_api_token');
|
|
|
|
if (!$api_token) {
|
|
return false;
|
|
}
|
|
|
|
$query_args = http_build_query($args);
|
|
$url = CANNAIQ_MENUS_API_URL . '/products?' . $query_args;
|
|
|
|
$response = wp_remote_get($url, [
|
|
'headers' => [
|
|
'X-API-Key' => $api_token
|
|
],
|
|
'timeout' => 30
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['products'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Fetch Single Product from API
|
|
*/
|
|
public function fetch_product($id) {
|
|
$api_token = get_option('cannaiq_api_token');
|
|
|
|
if (!$api_token) {
|
|
return false;
|
|
}
|
|
|
|
$url = CANNAIQ_MENUS_API_URL . '/products/' . intval($id);
|
|
|
|
$response = wp_remote_get($url, [
|
|
'headers' => [
|
|
'X-API-Key' => $api_token
|
|
],
|
|
'timeout' => 30
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['product'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Fetch Categories from API
|
|
*/
|
|
public function fetch_categories($args = []) {
|
|
$api_token = get_option('cannaiq_api_token');
|
|
|
|
if (!$api_token) {
|
|
return false;
|
|
}
|
|
|
|
$query_args = http_build_query($args);
|
|
$url = CANNAIQ_MENUS_API_URL . '/categories' . ($query_args ? '?' . $query_args : '');
|
|
|
|
$response = wp_remote_get($url, [
|
|
'headers' => [
|
|
'X-API-Key' => $api_token
|
|
],
|
|
'timeout' => 30
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['categories'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Fetch Brands from API
|
|
*/
|
|
public function fetch_brands($args = []) {
|
|
$api_token = get_option('cannaiq_api_token');
|
|
|
|
if (!$api_token) {
|
|
return false;
|
|
}
|
|
|
|
$query_args = http_build_query($args);
|
|
$url = CANNAIQ_MENUS_API_URL . '/brands' . ($query_args ? '?' . $query_args : '');
|
|
|
|
$response = wp_remote_get($url, [
|
|
'headers' => [
|
|
'X-API-Key' => $api_token
|
|
],
|
|
'timeout' => 30
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['brands'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Fetch Specials/Deals from API
|
|
*/
|
|
public function fetch_specials($args = []) {
|
|
$api_token = get_option('cannaiq_api_token');
|
|
|
|
if (!$api_token) {
|
|
return false;
|
|
}
|
|
|
|
$query_args = http_build_query($args);
|
|
$url = CANNAIQ_MENUS_API_URL . '/specials' . ($query_args ? '?' . $query_args : '');
|
|
|
|
$response = wp_remote_get($url, [
|
|
'headers' => [
|
|
'X-API-Key' => $api_token
|
|
],
|
|
'timeout' => 30
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
return $data['products'] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Get categories as options for Elementor select control
|
|
* Returns cached results for performance
|
|
*/
|
|
public function get_category_options() {
|
|
$cache_key = 'cannaiq_category_options';
|
|
$cached = get_transient($cache_key);
|
|
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
|
|
$categories = $this->fetch_categories();
|
|
$options = ['' => __('All Categories', 'cannaiq-menus')];
|
|
|
|
if ($categories) {
|
|
foreach ($categories as $cat) {
|
|
$name = $cat['type'] ?? $cat['name'] ?? '';
|
|
if ($name) {
|
|
$options[$name] = ucwords(str_replace('_', ' ', $name));
|
|
}
|
|
}
|
|
}
|
|
|
|
set_transient($cache_key, $options, 5 * MINUTE_IN_SECONDS);
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Get brands as options for Elementor select control
|
|
* Returns cached results for performance
|
|
*/
|
|
public function get_brand_options() {
|
|
$cache_key = 'cannaiq_brand_options';
|
|
$cached = get_transient($cache_key);
|
|
|
|
if ($cached !== false) {
|
|
return $cached;
|
|
}
|
|
|
|
$brands = $this->fetch_brands(['limit' => 200]);
|
|
$options = ['' => __('All Brands', 'cannaiq-menus')];
|
|
|
|
if ($brands) {
|
|
foreach ($brands as $brand) {
|
|
$name = $brand['brand'] ?? $brand['brand_name'] ?? '';
|
|
if ($name) {
|
|
$options[$name] = $name;
|
|
}
|
|
}
|
|
}
|
|
|
|
set_transient($cache_key, $options, 5 * MINUTE_IN_SECONDS);
|
|
return $options;
|
|
}
|
|
}
|
|
|
|
// Initialize Plugin
|
|
function cannaiq_menus() {
|
|
return CannaIQ_Menus_Plugin::instance();
|
|
}
|
|
|
|
cannaiq_menus();
|