feat: Rename WordPress plugin to CannaIQ Menus v1.5.3

- Rename plugin from Crawlsy Menus to CannaIQ Menus
- Update version to 1.5.3
- Update text domain to cannaiq-menus
- Rename all CSS classes from crawlsy-* to cannaiq-*
- Update shortcodes to [cannaiq_products] and [cannaiq_product]
- Add backward compatibility for legacy shortcodes
- Update download links on Home and LandingPage
- Fix health panel Redis timeout issue
- Add clear error message when backend not running

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-08 00:24:43 -07:00
parent bf4ceaf09e
commit 948a732dd5
12 changed files with 1010 additions and 241 deletions

Binary file not shown.

View File

@@ -440,7 +440,7 @@ export function Home() {
Go to /admin
</Link>
<a
href="/downloads/crawlsy-menus-v1.3.0.zip"
href="/downloads/cannaiq-menus-1.5.3.zip"
className="flex items-center justify-center gap-2 border-2 border-emerald-600 text-emerald-700 font-semibold py-3 px-6 rounded-lg hover:bg-emerald-50 transition-colors"
>
<Code className="w-5 h-5" />
@@ -497,7 +497,7 @@ export function Home() {
<div className="flex items-center gap-6 text-gray-400 text-sm">
<Link to="/login" className="hover:text-white transition-colors">Sign in</Link>
<a href="mailto:hello@cannaiq.co" className="hover:text-white transition-colors">Contact</a>
<a href="/downloads/crawlsy-menus-v1.3.0.zip" className="hover:text-white transition-colors">WordPress Plugin</a>
<a href="/downloads/cannaiq-menus-1.5.3.zip" className="hover:text-white transition-colors">WordPress Plugin</a>
</div>
</div>

View File

@@ -23,7 +23,7 @@ export default function LandingPage() {
<Link to="/login" className="px-8 py-3 bg-white text-emerald-700 font-semibold rounded-lg hover:bg-gray-100 transition-colors shadow-lg">
Sign In
</Link>
<a href="/downloads/crawlsy-menus-v1.3.0.zip" className="px-8 py-3 border-2 border-white text-white font-semibold rounded-lg hover:bg-white hover:text-emerald-700 transition-colors">
<a href="/downloads/cannaiq-menus-1.5.3.zip" className="px-8 py-3 border-2 border-white text-white font-semibold rounded-lg hover:bg-white hover:text-emerald-700 transition-colors">
Download WordPress Plugin
</a>
</div>
@@ -74,20 +74,20 @@ export default function LandingPage() {
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl font-bold text-gray-900 mb-6">WordPress Integration</h2>
<p className="text-lg text-gray-600 mb-8">
Display your dispensary menu directly on your WordPress site with our Crawlsy Menus plugin.
Display your dispensary menu directly on your WordPress site with our CannaIQ Menus plugin.
Features Elementor widgets and shortcode support for maximum flexibility.
</p>
<div className="bg-gray-900 rounded-xl p-6 text-left mb-8 font-mono text-sm">
<div className="text-gray-400 mb-2">// Display products grid</div>
<div className="text-emerald-400">[crawlsy_products limit="12" columns="3"]</div>
<div className="text-emerald-400">[cannaiq_products limit="12" columns="3"]</div>
<div className="text-gray-400 mt-4 mb-2">// Display single product</div>
<div className="text-emerald-400">[crawlsy_product id="123"]</div>
<div className="text-emerald-400">[cannaiq_product id="123"]</div>
</div>
<a
href="/downloads/crawlsy-menus-v1.3.0.zip"
href="/downloads/cannaiq-menus-1.5.3.zip"
className="inline-block px-8 py-3 bg-emerald-600 text-white font-semibold rounded-lg hover:bg-emerald-700 transition-colors shadow-lg"
>
Download Crawlsy Menus v1.3.0
Download CannaIQ Menus v1.5.3
</a>
</div>
</div>

View File

@@ -3,7 +3,7 @@
# API URL for dispensary data endpoints (public API)
# Local development: http://localhost:3010
# Production: https://dispos.crawlsy.com (or your production API URL)
# Production: https://cannaiq.co (or your production API URL)
REACT_APP_DATA_API_URL=http://localhost:3010
# API Key for accessing the /api/v1/* endpoints

View File

@@ -0,0 +1,314 @@
/**
* CannaIQ Menus - WordPress Plugin Styles
* v1.5.3
*/
/* Product Grid */
.cannaiq-product-grid {
display: grid;
gap: 24px;
margin: 20px 0;
}
.cannaiq-grid-cols-2 {
grid-template-columns: repeat(2, 1fr);
}
.cannaiq-grid-cols-3 {
grid-template-columns: repeat(3, 1fr);
}
.cannaiq-grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
.cannaiq-grid-cols-6 {
grid-template-columns: repeat(6, 1fr);
}
@media (max-width: 1024px) {
.cannaiq-grid-cols-4,
.cannaiq-grid-cols-6 {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.cannaiq-grid-cols-3,
.cannaiq-grid-cols-4,
.cannaiq-grid-cols-6 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.cannaiq-product-grid {
grid-template-columns: 1fr;
}
}
/* Product Card */
.cannaiq-product-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
flex-direction: column;
}
.cannaiq-product-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.cannaiq-product-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}
.cannaiq-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cannaiq-product-content {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.cannaiq-product-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px 0;
color: #333;
line-height: 1.4;
}
.cannaiq-product-brand {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.cannaiq-product-description {
font-size: 14px;
color: #666;
line-height: 1.6;
margin: 0 0 12px 0;
}
.cannaiq-product-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 12px 0;
}
.cannaiq-meta-item {
font-size: 13px;
padding: 4px 10px;
border-radius: 4px;
background: #f0f0f0;
}
.cannaiq-meta-item strong {
color: #333;
}
.cannaiq-thc {
background: #e8f5e9;
color: #2e7d32;
}
.cannaiq-cbd {
background: #e3f2fd;
color: #1565c0;
}
.cannaiq-product-price {
font-size: 20px;
font-weight: 700;
color: #2e7d32;
margin-top: auto;
padding-top: 12px;
}
.cannaiq-price-sale {
color: #c62828;
}
.cannaiq-price-regular.cannaiq-strikethrough {
text-decoration: line-through;
color: #999;
font-size: 16px;
margin-left: 8px;
}
.cannaiq-out-of-stock {
display: inline-block;
padding: 6px 12px;
background: #ffebee;
color: #c62828;
font-size: 13px;
font-weight: 600;
border-radius: 4px;
margin-top: 8px;
}
/* Single Product */
.cannaiq-single-product {
background: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 20px 0;
}
.cannaiq-layout-horizontal {
display: grid;
grid-template-columns: 400px 1fr;
gap: 32px;
}
.cannaiq-layout-vertical {
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 768px) {
.cannaiq-layout-horizontal {
grid-template-columns: 1fr;
}
}
.cannaiq-single-product-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
border-radius: 8px;
background: #f5f5f5;
}
.cannaiq-single-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cannaiq-single-product-details {
display: flex;
flex-direction: column;
gap: 16px;
}
.cannaiq-single-product-title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: #333;
line-height: 1.3;
}
.cannaiq-single-product-brand {
font-size: 16px;
color: #666;
}
.cannaiq-single-product-brand strong {
color: #333;
}
.cannaiq-single-product-description {
font-size: 16px;
line-height: 1.6;
color: #555;
}
.cannaiq-single-product-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
padding: 16px;
background: #f8f8f8;
border-radius: 8px;
}
.cannaiq-info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.cannaiq-info-item strong {
font-size: 14px;
color: #333;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.cannaiq-info-item span {
font-size: 16px;
color: #555;
}
.cannaiq-info-item.cannaiq-terpenes span,
.cannaiq-info-item.cannaiq-effects span,
.cannaiq-info-item.cannaiq-flavors span {
font-size: 14px;
}
.cannaiq-single-product-price {
font-size: 32px;
font-weight: 700;
color: #2e7d32;
}
/* Loading State */
.cannaiq-loading {
text-align: center;
padding: 40px;
color: #666;
}
.cannaiq-loading:after {
content: "...";
animation: cannaiq-dots 1.5s steps(4, end) infinite;
}
@keyframes cannaiq-dots {
0%, 20% {
color: rgba(0, 0, 0, 0);
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0),
0.5em 0 0 rgba(0, 0, 0, 0);
}
40% {
color: #666;
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0),
0.5em 0 0 rgba(0, 0, 0, 0);
}
60% {
text-shadow: 0.25em 0 0 #666,
0.5em 0 0 rgba(0, 0, 0, 0);
}
80%, 100% {
text-shadow: 0.25em 0 0 #666,
0.5em 0 0 #666;
}
}
/* Error State */
.cannaiq-error {
padding: 16px;
background: #ffebee;
color: #c62828;
border-radius: 4px;
border-left: 4px solid #c62828;
}

View File

@@ -0,0 +1,58 @@
/**
* CannaIQ Menus - WordPress Plugin JavaScript
* v1.5.3
*/
(function($) {
'use strict';
/**
* Initialize plugin
*/
$(document).ready(function() {
// Lazy load images
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
}
});
});
document.querySelectorAll('.cannaiq-product-image img[data-src]').forEach(img => {
imageObserver.observe(img);
});
}
// Add animation to product cards on scroll
if ('IntersectionObserver' in window) {
const cardObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '0';
entry.target.style.transform = 'translateY(20px)';
setTimeout(() => {
entry.target.style.transition = 'opacity 0.5s, transform 0.5s';
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, 10);
cardObserver.unobserve(entry.target);
}
});
}, {
threshold: 0.1
});
document.querySelectorAll('.cannaiq-product-card').forEach(card => {
cardObserver.observe(card);
});
}
});
})(jQuery);

View File

@@ -1,23 +1,23 @@
#!/bin/bash
# WordPress Plugin Build Script
# Builds the plugin zip with the correct naming convention: cb-wpmenu-{version}.zip
# Builds the plugin zip with the correct naming convention: cannaiq-menus-{version}.zip
set -e
# Get the version from the main plugin file
VERSION=$(grep -oP "Version:\s*\K[0-9.]+" crawlsy-menus.php)
VERSION=$(grep -oP "Version:\s*\K[0-9.]+" cannaiq-menus.php)
if [ -z "$VERSION" ]; then
echo "Error: Could not extract version from crawlsy-menus.php"
echo "Error: Could not extract version from cannaiq-menus.php"
exit 1
fi
# Define paths
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT_DIR="${PLUGIN_DIR}/../backend/public/downloads"
OUTPUT_FILE="cb-wpmenu-${VERSION}.zip"
OUTPUT_FILE="cannaiq-menus-${VERSION}.zip"
echo "Building WordPress plugin..."
echo "Building CannaIQ Menus WordPress plugin..."
echo " Version: ${VERSION}"
echo " Output: ${OUTPUT_DIR}/${OUTPUT_FILE}"
@@ -27,13 +27,18 @@ mkdir -p "${OUTPUT_DIR}"
# Create the zip file (from the plugin directory)
cd "${PLUGIN_DIR}"
rm -f "${OUTPUT_DIR}/${OUTPUT_FILE}"
zip -r "${OUTPUT_DIR}/${OUTPUT_FILE}" . -x "*.git*" -x "build-plugin.sh"
# Exclude old/legacy files and build script
zip -r "${OUTPUT_DIR}/${OUTPUT_FILE}" . \
-x "*.git*" \
-x "build-plugin.sh" \
-x "crawlsy-menus.php" \
-x "assets/css/crawlsy-menus.css" \
-x "assets/js/crawlsy-menus.js"
echo ""
echo "Build complete!"
echo " File: ${OUTPUT_DIR}/${OUTPUT_FILE}"
echo " Size: $(ls -lh "${OUTPUT_DIR}/${OUTPUT_FILE}" | awk '{print $5}')"
echo ""
echo "IMPORTANT: Update frontend/src/pages/LandingPage.tsx with the new version:"
echo " href=\"/downloads/cb-wpmenu-${VERSION}.zip\""
echo " Download Plugin v${VERSION}"
echo "Download URL: https://cannaiq.co/downloads/cannaiq-menus-${VERSION}.zip"

View File

@@ -0,0 +1,402 @@
<?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: 1.5.3
* 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', '1.5.3');
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/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']);
}
public function init() {
// Initialize plugin
load_plugin_textdomain('cannaiq-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
// Register shortcodes
add_shortcode('cannaiq_products', [$this, 'products_shortcode']);
add_shortcode('cannaiq_product', [$this, 'single_product_shortcode']);
// Legacy shortcode support (backward compatibility)
add_shortcode('crawlsy_products', [$this, 'products_shortcode']);
add_shortcode('crawlsy_product', [$this, 'single_product_shortcode']);
add_shortcode('dutchie_products', [$this, 'products_shortcode']);
add_shortcode('dutchie_product', [$this, 'single_product_shortcode']);
}
/**
* Register Elementor Widgets
*/
public function register_elementor_widgets($widgets_manager) {
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-grid.php';
require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/single-product.php';
$widgets_manager->register(new \CannaIQ_Menus_Product_Grid_Widget());
$widgets_manager->register(new \CannaIQ_Menus_Single_Product_Widget());
}
/**
* Enqueue Scripts and Styles
*/
public function enqueue_scripts() {
wp_enqueue_style(
'cannaiq-menus-styles',
CANNAIQ_MENUS_PLUGIN_URL . 'assets/css/cannaiq-menus.css',
[],
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');
// Migrate old settings if they exist
$old_crawlsy_token = get_option('crawlsy_api_token');
$old_dutchie_token = get_option('dutchie_api_token');
if (!get_option('cannaiq_api_token')) {
if ($old_crawlsy_token) {
update_option('cannaiq_api_token', $old_crawlsy_token);
} elseif ($old_dutchie_token) {
update_option('cannaiq_api_token', $old_dutchie_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: 800px;">
<thead>
<tr>
<th>Shortcode</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[cannaiq_products]</code></td>
<td>Display a grid of products. Options: <code>category_id</code>, <code>limit</code>, <code>columns</code>, <code>in_stock</code></td>
</tr>
<tr>
<td><code>[cannaiq_product id="123"]</code></td>
<td>Display a single product by ID</td>
</tr>
</tbody>
</table>
<h3>Elementor Widgets</h3>
<p>If you have Elementor installed, you can use the CannaIQ widgets:</p>
<ul style="list-style: disc; margin-left: 20px;">
<li><strong>CannaIQ Product Grid</strong> - Display a grid of products with filtering options</li>
<li><strong>CannaIQ Single Product</strong> - Display a single product card</li>
</ul>
</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();
}
/**
* 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;
}
}
// Initialize Plugin
function cannaiq_menus() {
return CannaIQ_Menus_Plugin::instance();
}
cannaiq_menus();

View File

@@ -10,62 +10,67 @@ if (!defined('ABSPATH')) {
$columns = isset($atts['columns']) ? $atts['columns'] : 3;
?>
<div class="crawlsy-product-grid crawlsy-grid-cols-<?php echo esc_attr($columns); ?>">
<div class="cannaiq-product-grid cannaiq-grid-cols-<?php echo esc_attr($columns); ?>">
<?php foreach ($products as $product):
$image_url = $product['image_url'] ?? $product['primary_image_url'] ?? $product['image_url_full'] ?? '';
$product_url = !empty($product['dutchie_url']) ? $product['dutchie_url'] : '#';
$image_url = $product['image_url'] ?? $product['primary_image_url'] ?? '';
$product_url = !empty($product['menu_url']) ? $product['menu_url'] : '#';
?>
<div class="crawlsy-product-card"
<div class="cannaiq-product-card"
<?php if ($product_url !== '#'): ?>onclick="window.open('<?php echo esc_url($product_url); ?>', '_blank')"<?php endif; ?>
style="cursor: <?php echo ($product_url !== '#') ? 'pointer' : 'default'; ?>;">
<?php if (!empty($image_url)): ?>
<div class="crawlsy-product-image">
<div class="cannaiq-product-image">
<img src="<?php echo esc_url($image_url); ?>"
alt="<?php echo esc_attr($product['name']); ?>"
loading="lazy" />
</div>
<?php endif; ?>
<div class="crawlsy-product-content">
<h3 class="crawlsy-product-title">
<div class="cannaiq-product-content">
<h3 class="cannaiq-product-title">
<?php echo esc_html($product['name']); ?>
</h3>
<?php if (!empty($product['brand'])): ?>
<div class="crawlsy-product-brand">
<div class="cannaiq-product-brand">
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<p class="crawlsy-product-description">
<p class="cannaiq-product-description">
<?php echo esc_html(wp_trim_words($product['description'], 15)); ?>
</p>
<?php endif; ?>
<div class="crawlsy-product-meta">
<?php if (!empty($product['metadata']['thc'])): ?>
<span class="crawlsy-meta-item crawlsy-thc">
<strong>THC:</strong> <?php echo esc_html($product['metadata']['thc']); ?>
<div class="cannaiq-product-meta">
<?php if (!empty($product['thc_percentage'])): ?>
<span class="cannaiq-meta-item cannaiq-thc">
<strong>THC:</strong> <?php echo esc_html($product['thc_percentage']); ?>%
</span>
<?php endif; ?>
<?php if (!empty($product['metadata']['cbd'])): ?>
<span class="crawlsy-meta-item crawlsy-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['metadata']['cbd']); ?>
<?php if (!empty($product['cbd_percentage'])): ?>
<span class="cannaiq-meta-item cannaiq-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['cbd_percentage']); ?>%
</span>
<?php endif; ?>
</div>
<?php if (isset($product['price'])): ?>
<div class="crawlsy-product-price">
$<?php echo number_format($product['price'], 2); ?>
<?php if (isset($product['regular_price'])): ?>
<div class="cannaiq-product-price">
<?php if (!empty($product['sale_price']) && $product['sale_price'] != $product['regular_price']): ?>
<span class="cannaiq-price-sale">$<?php echo esc_html($product['sale_price']); ?></span>
<span class="cannaiq-price-regular cannaiq-strikethrough">$<?php echo esc_html($product['regular_price']); ?></span>
<?php else: ?>
$<?php echo esc_html($product['regular_price']); ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="crawlsy-out-of-stock">
<?php _e('Out of Stock', 'crawlsy-menus'); ?>
<div class="cannaiq-out-of-stock">
<?php _e('Out of Stock', 'cannaiq-menus'); ?>
</div>
<?php endif; ?>
</div>

View File

@@ -6,92 +6,77 @@
if (!defined('ABSPATH')) {
exit;
}
$image_url = $product['image_url'] ?? '';
?>
<div class="crawlsy-single-product crawlsy-layout-horizontal">
<?php if (!empty($product['image_url_full'])): ?>
<div class="crawlsy-single-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
<div class="cannaiq-single-product cannaiq-layout-horizontal">
<?php if (!empty($image_url)): ?>
<div class="cannaiq-single-product-image">
<img src="<?php echo esc_url($image_url); ?>"
alt="<?php echo esc_attr($product['name']); ?>" />
</div>
<?php endif; ?>
<div class="crawlsy-single-product-details">
<h2 class="crawlsy-single-product-title">
<div class="cannaiq-single-product-details">
<h2 class="cannaiq-single-product-title">
<?php echo esc_html($product['name']); ?>
</h2>
<?php if (!empty($product['brand'])): ?>
<div class="crawlsy-single-product-brand">
<strong><?php _e('Brand:', 'crawlsy-menus'); ?></strong>
<div class="cannaiq-single-product-brand">
<strong><?php _e('Brand:', 'cannaiq-menus'); ?></strong>
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<div class="crawlsy-single-product-description">
<div class="cannaiq-single-product-description">
<?php echo wp_kses_post(nl2br($product['description'])); ?>
</div>
<?php endif; ?>
<div class="crawlsy-single-product-info">
<?php if (!empty($product['metadata']['thc'])): ?>
<div class="crawlsy-info-item">
<strong><?php _e('THC:', 'crawlsy-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['thc']); ?></span>
<div class="cannaiq-single-product-info">
<?php if (!empty($product['thc_percentage'])): ?>
<div class="cannaiq-info-item">
<strong><?php _e('THC:', 'cannaiq-menus'); ?></strong>
<span><?php echo esc_html($product['thc_percentage']); ?>%</span>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['cbd'])): ?>
<div class="crawlsy-info-item">
<strong><?php _e('CBD:', 'crawlsy-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['cbd']); ?></span>
<?php if (!empty($product['cbd_percentage'])): ?>
<div class="cannaiq-info-item">
<strong><?php _e('CBD:', 'cannaiq-menus'); ?></strong>
<span><?php echo esc_html($product['cbd_percentage']); ?>%</span>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['terpenes'])): ?>
<div class="crawlsy-info-item crawlsy-terpenes">
<strong><?php _e('Terpenes:', 'crawlsy-menus'); ?></strong>
<?php if (is_array($product['metadata']['terpenes'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['terpenes'])); ?></span>
<?php if (!empty($product['effects'])): ?>
<div class="cannaiq-info-item cannaiq-effects">
<strong><?php _e('Effects:', 'cannaiq-menus'); ?></strong>
<?php if (is_array($product['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['terpenes']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['effects'])): ?>
<div class="crawlsy-info-item crawlsy-effects">
<strong><?php _e('Effects:', 'crawlsy-menus'); ?></strong>
<?php if (is_array($product['metadata']['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['effects']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['flavors'])): ?>
<div class="crawlsy-info-item crawlsy-flavors">
<strong><?php _e('Flavors:', 'crawlsy-menus'); ?></strong>
<?php if (is_array($product['metadata']['flavors'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['flavors'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['flavors']); ?></span>
<span><?php echo esc_html($product['effects']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php if (isset($product['price'])): ?>
<div class="crawlsy-single-product-price">
$<?php echo number_format($product['price'], 2); ?>
<?php if (isset($product['regular_price'])): ?>
<div class="cannaiq-single-product-price">
<?php if (!empty($product['sale_price']) && $product['sale_price'] != $product['regular_price']): ?>
<span class="cannaiq-price-sale">$<?php echo esc_html($product['sale_price']); ?></span>
<span class="cannaiq-price-regular cannaiq-strikethrough">$<?php echo esc_html($product['regular_price']); ?></span>
<?php else: ?>
$<?php echo esc_html($product['regular_price']); ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="crawlsy-out-of-stock">
<?php _e('Out of Stock', 'crawlsy-menus'); ?>
<div class="cannaiq-out-of-stock">
<?php _e('Out of Stock', 'cannaiq-menus'); ?>
</div>
<?php endif; ?>
</div>

View File

@@ -7,14 +7,14 @@ if (!defined('ABSPATH')) {
exit;
}
class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
class CannaIQ_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'crawlsy_product_grid';
return 'cannaiq_product_grid';
}
public function get_title() {
return __('Crawlsy Product Grid', 'crawlsy-menus');
return __('CannaIQ Product Grid', 'cannaiq-menus');
}
public function get_icon() {
@@ -31,7 +31,7 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'crawlsy-menus'),
'label' => __('Content', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -39,9 +39,9 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'store_id',
[
'label' => __('Store ID', 'crawlsy-menus'),
'label' => __('Store ID', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => get_option('crawlsy_default_store_id', 1),
'default' => get_option('cannaiq_default_store_id', 1),
'min' => 1,
]
);
@@ -49,17 +49,17 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'category_id',
[
'label' => __('Category ID', 'crawlsy-menus'),
'label' => __('Category ID', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => '',
'description' => __('Leave empty to show all categories', 'crawlsy-menus'),
'description' => __('Leave empty to show all categories', 'cannaiq-menus'),
]
);
$this->add_control(
'limit',
[
'label' => __('Number of Products', 'crawlsy-menus'),
'label' => __('Number of Products', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 12,
'min' => 1,
@@ -70,14 +70,14 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'columns',
[
'label' => __('Columns', 'crawlsy-menus'),
'label' => __('Columns', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => '3',
'options' => [
'2' => __('2 Columns', 'crawlsy-menus'),
'3' => __('3 Columns', 'crawlsy-menus'),
'4' => __('4 Columns', 'crawlsy-menus'),
'6' => __('6 Columns', 'crawlsy-menus'),
'2' => __('2 Columns', 'cannaiq-menus'),
'3' => __('3 Columns', 'cannaiq-menus'),
'4' => __('4 Columns', 'cannaiq-menus'),
'6' => __('6 Columns', 'cannaiq-menus'),
],
]
);
@@ -85,10 +85,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'in_stock_only',
[
'label' => __('In Stock Only', 'crawlsy-menus'),
'label' => __('In Stock Only', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -97,10 +97,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'search',
[
'label' => __('Search Filter', 'crawlsy-menus'),
'label' => __('Search Filter', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => '',
'description' => __('Filter products by name', 'crawlsy-menus'),
'description' => __('Filter products by name', 'cannaiq-menus'),
]
);
@@ -110,7 +110,7 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'display_section',
[
'label' => __('Display Options', 'crawlsy-menus'),
'label' => __('Display Options', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -118,10 +118,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_image',
[
'label' => __('Show Image', 'crawlsy-menus'),
'label' => __('Show Image', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -130,10 +130,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'crawlsy-menus'),
'label' => __('Show Price', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -142,10 +142,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_thc',
[
'label' => __('Show THC', 'crawlsy-menus'),
'label' => __('Show THC', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -154,10 +154,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_cbd',
[
'label' => __('Show CBD', 'crawlsy-menus'),
'label' => __('Show CBD', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -166,10 +166,10 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_description',
[
'label' => __('Show Description', 'crawlsy-menus'),
'label' => __('Show Description', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'no',
]
@@ -181,7 +181,7 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'style_section',
[
'label' => __('Style', 'crawlsy-menus'),
'label' => __('Style', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
@@ -189,11 +189,11 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'card_background',
[
'label' => __('Card Background', 'crawlsy-menus'),
'label' => __('Card Background', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .crawlsy-product-card' => 'background-color: {{VALUE}};',
'{{WRAPPER}} .cannaiq-product-card' => 'background-color: {{VALUE}};',
],
]
);
@@ -201,7 +201,7 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'card_border_radius',
[
'label' => __('Border Radius', 'crawlsy-menus'),
'label' => __('Border Radius', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
@@ -214,7 +214,7 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
'size' => 8,
],
'selectors' => [
'{{WRAPPER}} .crawlsy-product-card' => 'border-radius: {{SIZE}}{{UNIT}};',
'{{WRAPPER}} .cannaiq-product-card' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
@@ -222,11 +222,11 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$this->add_control(
'text_color',
[
'label' => __('Text Color', 'crawlsy-menus'),
'label' => __('Text Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#333333',
'selectors' => [
'{{WRAPPER}} .crawlsy-product-card' => 'color: {{VALUE}};',
'{{WRAPPER}} .cannaiq-product-card' => 'color: {{VALUE}};',
],
]
);
@@ -251,66 +251,71 @@ class Crawlsy_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
$args['search'] = $settings['search'];
}
$plugin = Crawlsy_Menus_Plugin::instance();
$plugin = CannaIQ_Menus_Plugin::instance();
$products = $plugin->fetch_products($args);
if (!$products) {
echo '<p>' . __('No products found.', 'crawlsy-menus') . '</p>';
echo '<p>' . __('No products found.', 'cannaiq-menus') . '</p>';
return;
}
$columns = $settings['columns'];
?>
<div class="crawlsy-product-grid crawlsy-grid-cols-<?php echo esc_attr($columns); ?>">
<div class="cannaiq-product-grid cannaiq-grid-cols-<?php echo esc_attr($columns); ?>">
<?php foreach ($products as $product):
$image_url = $product['image_url'] ?? $product['primary_image_url'] ?? $product['image_url_full'] ?? '';
$product_url = !empty($product['dutchie_url']) ? $product['dutchie_url'] : '#';
$product_url = !empty($product['menu_url']) ? $product['menu_url'] : '#';
?>
<div class="crawlsy-product-card"
<div class="cannaiq-product-card"
<?php if ($product_url !== '#'): ?>onclick="window.open('<?php echo esc_url($product_url); ?>', '_blank')"<?php endif; ?>
style="cursor: <?php echo ($product_url !== '#') ? 'pointer' : 'default'; ?>;">
<?php if ($settings['show_image'] === 'yes' && !empty($image_url)): ?>
<div class="crawlsy-product-image">
<div class="cannaiq-product-image">
<img src="<?php echo esc_url($image_url); ?>"
alt="<?php echo esc_attr($product['name']); ?>"
loading="lazy" />
</div>
<?php endif; ?>
<div class="crawlsy-product-content">
<h3 class="crawlsy-product-title">
<div class="cannaiq-product-content">
<h3 class="cannaiq-product-title">
<?php echo esc_html($product['name']); ?>
</h3>
<?php if ($settings['show_description'] === 'yes' && !empty($product['description'])): ?>
<p class="crawlsy-product-description">
<p class="cannaiq-product-description">
<?php echo esc_html(wp_trim_words($product['description'], 15)); ?>
</p>
<?php endif; ?>
<div class="crawlsy-product-meta">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['metadata']['thc'])): ?>
<span class="crawlsy-meta-item crawlsy-thc">
<strong>THC:</strong> <?php echo esc_html($product['metadata']['thc']); ?>
<div class="cannaiq-product-meta">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['thc_percentage'])): ?>
<span class="cannaiq-meta-item cannaiq-thc">
<strong>THC:</strong> <?php echo esc_html($product['thc_percentage']); ?>%
</span>
<?php endif; ?>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['metadata']['cbd'])): ?>
<span class="crawlsy-meta-item crawlsy-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['metadata']['cbd']); ?>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['cbd_percentage'])): ?>
<span class="cannaiq-meta-item cannaiq-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['cbd_percentage']); ?>%
</span>
<?php endif; ?>
</div>
<?php if ($settings['show_price'] === 'yes' && isset($product['price'])): ?>
<div class="crawlsy-product-price">
$<?php echo number_format($product['price'], 2); ?>
<?php if ($settings['show_price'] === 'yes' && isset($product['regular_price'])): ?>
<div class="cannaiq-product-price">
<?php if (!empty($product['sale_price']) && $product['sale_price'] != $product['regular_price']): ?>
<span class="cannaiq-price-sale">$<?php echo esc_html($product['sale_price']); ?></span>
<span class="cannaiq-price-regular cannaiq-strikethrough">$<?php echo esc_html($product['regular_price']); ?></span>
<?php else: ?>
$<?php echo esc_html($product['regular_price']); ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="crawlsy-out-of-stock">
<?php _e('Out of Stock', 'crawlsy-menus'); ?>
<div class="cannaiq-out-of-stock">
<?php _e('Out of Stock', 'cannaiq-menus'); ?>
</div>
<?php endif; ?>
</div>

View File

@@ -7,14 +7,14 @@ if (!defined('ABSPATH')) {
exit;
}
class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
class CannaIQ_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'crawlsy_single_product';
return 'cannaiq_single_product';
}
public function get_title() {
return __('Crawlsy Single Product', 'crawlsy-menus');
return __('CannaIQ Single Product', 'cannaiq-menus');
}
public function get_icon() {
@@ -31,9 +31,9 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
* @return array Associative array of product_id => product_name
*/
protected function get_products_for_select() {
$options = ['' => __('-- Select a Product --', 'crawlsy-menus')];
$options = ['' => __('-- Select a Product --', 'cannaiq-menus')];
$plugin = Crawlsy_Menus_Plugin::instance();
$plugin = CannaIQ_Menus_Plugin::instance();
$products = $plugin->fetch_products(['limit' => 500]);
if ($products && is_array($products)) {
@@ -58,7 +58,7 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'crawlsy-menus'),
'label' => __('Content', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -69,22 +69,22 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'product_id',
[
'label' => __('Select Product', 'crawlsy-menus'),
'label' => __('Select Product', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SELECT2,
'options' => $products_options,
'default' => '',
'label_block' => true,
'description' => __('Search and select a product to display', 'crawlsy-menus'),
'description' => __('Search and select a product to display', 'cannaiq-menus'),
]
);
$this->add_control(
'product_id_manual',
[
'label' => __('Or Enter Product ID', 'crawlsy-menus'),
'label' => __('Or Enter Product ID', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => '',
'description' => __('Manually enter a product ID if not found in dropdown', 'crawlsy-menus'),
'description' => __('Manually enter a product ID if not found in dropdown', 'cannaiq-menus'),
'condition' => [
'product_id' => '',
],
@@ -94,12 +94,12 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'layout',
[
'label' => __('Layout', 'crawlsy-menus'),
'label' => __('Layout', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'horizontal',
'options' => [
'horizontal' => __('Horizontal', 'crawlsy-menus'),
'vertical' => __('Vertical', 'crawlsy-menus'),
'horizontal' => __('Horizontal', 'cannaiq-menus'),
'vertical' => __('Vertical', 'cannaiq-menus'),
],
]
);
@@ -110,7 +110,7 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'display_section',
[
'label' => __('Display Options', 'crawlsy-menus'),
'label' => __('Display Options', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -118,10 +118,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_image',
[
'label' => __('Show Image', 'crawlsy-menus'),
'label' => __('Show Image', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -130,10 +130,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'crawlsy-menus'),
'label' => __('Show Price', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -142,10 +142,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_description',
[
'label' => __('Show Description', 'crawlsy-menus'),
'label' => __('Show Description', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -154,10 +154,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_thc',
[
'label' => __('Show THC', 'crawlsy-menus'),
'label' => __('Show THC', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -166,10 +166,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_cbd',
[
'label' => __('Show CBD', 'crawlsy-menus'),
'label' => __('Show CBD', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -178,10 +178,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_terpenes',
[
'label' => __('Show Terpenes', 'crawlsy-menus'),
'label' => __('Show Terpenes', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -190,10 +190,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_effects',
[
'label' => __('Show Effects', 'crawlsy-menus'),
'label' => __('Show Effects', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -202,10 +202,10 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'show_brand',
[
'label' => __('Show Brand', 'crawlsy-menus'),
'label' => __('Show Brand', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'crawlsy-menus'),
'label_off' => __('No', 'crawlsy-menus'),
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
@@ -217,7 +217,7 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->start_controls_section(
'style_section',
[
'label' => __('Style', 'crawlsy-menus'),
'label' => __('Style', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
@@ -225,11 +225,11 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'background_color',
[
'label' => __('Background Color', 'crawlsy-menus'),
'label' => __('Background Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .crawlsy-single-product' => 'background-color: {{VALUE}};',
'{{WRAPPER}} .cannaiq-single-product' => 'background-color: {{VALUE}};',
],
]
);
@@ -237,7 +237,7 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'border_radius',
[
'label' => __('Border Radius', 'crawlsy-menus'),
'label' => __('Border Radius', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
@@ -250,7 +250,7 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
'size' => 8,
],
'selectors' => [
'{{WRAPPER}} .crawlsy-single-product' => 'border-radius: {{SIZE}}{{UNIT}};',
'{{WRAPPER}} .cannaiq-single-product' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
@@ -258,11 +258,11 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$this->add_control(
'text_color',
[
'label' => __('Text Color', 'crawlsy-menus'),
'label' => __('Text Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#333333',
'selectors' => [
'{{WRAPPER}} .crawlsy-single-product' => 'color: {{VALUE}};',
'{{WRAPPER}} .cannaiq-single-product' => 'color: {{VALUE}};',
],
]
);
@@ -277,93 +277,88 @@ class Crawlsy_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
$product_id = !empty($settings['product_id']) ? $settings['product_id'] : $settings['product_id_manual'];
if (empty($product_id)) {
echo '<p>' . __('Please select or enter a product ID.', 'crawlsy-menus') . '</p>';
echo '<p>' . __('Please select or enter a product ID.', 'cannaiq-menus') . '</p>';
return;
}
$plugin = Crawlsy_Menus_Plugin::instance();
$plugin = CannaIQ_Menus_Plugin::instance();
$product = $plugin->fetch_product($product_id);
if (!$product) {
echo '<p>' . __('Product not found.', 'crawlsy-menus') . '</p>';
echo '<p>' . __('Product not found.', 'cannaiq-menus') . '</p>';
return;
}
$layout = $settings['layout'];
$image_url = $product['image_url'] ?? '';
?>
<div class="crawlsy-single-product crawlsy-layout-<?php echo esc_attr($layout); ?>">
<?php if ($settings['show_image'] === 'yes' && !empty($product['image_url_full'])): ?>
<div class="crawlsy-single-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
<div class="cannaiq-single-product cannaiq-layout-<?php echo esc_attr($layout); ?>">
<?php if ($settings['show_image'] === 'yes' && !empty($image_url)): ?>
<div class="cannaiq-single-product-image">
<img src="<?php echo esc_url($image_url); ?>"
alt="<?php echo esc_attr($product['name']); ?>" />
</div>
<?php endif; ?>
<div class="crawlsy-single-product-details">
<h2 class="crawlsy-single-product-title">
<div class="cannaiq-single-product-details">
<h2 class="cannaiq-single-product-title">
<?php echo esc_html($product['name']); ?>
</h2>
<?php if ($settings['show_brand'] === 'yes' && !empty($product['brand'])): ?>
<div class="crawlsy-single-product-brand">
<strong><?php _e('Brand:', 'crawlsy-menus'); ?></strong>
<div class="cannaiq-single-product-brand">
<strong><?php _e('Brand:', 'cannaiq-menus'); ?></strong>
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if ($settings['show_description'] === 'yes' && !empty($product['description'])): ?>
<div class="crawlsy-single-product-description">
<div class="cannaiq-single-product-description">
<?php echo wp_kses_post(nl2br($product['description'])); ?>
</div>
<?php endif; ?>
<div class="crawlsy-single-product-info">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['metadata']['thc'])): ?>
<div class="crawlsy-info-item">
<strong><?php _e('THC:', 'crawlsy-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['thc']); ?></span>
<div class="cannaiq-single-product-info">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['thc_percentage'])): ?>
<div class="cannaiq-info-item">
<strong><?php _e('THC:', 'cannaiq-menus'); ?></strong>
<span><?php echo esc_html($product['thc_percentage']); ?>%</span>
</div>
<?php endif; ?>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['metadata']['cbd'])): ?>
<div class="crawlsy-info-item">
<strong><?php _e('CBD:', 'crawlsy-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['cbd']); ?></span>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['cbd_percentage'])): ?>
<div class="cannaiq-info-item">
<strong><?php _e('CBD:', 'cannaiq-menus'); ?></strong>
<span><?php echo esc_html($product['cbd_percentage']); ?>%</span>
</div>
<?php endif; ?>
<?php if ($settings['show_terpenes'] === 'yes' && !empty($product['metadata']['terpenes'])): ?>
<div class="crawlsy-info-item crawlsy-terpenes">
<strong><?php _e('Terpenes:', 'crawlsy-menus'); ?></strong>
<?php if (is_array($product['metadata']['terpenes'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['terpenes'])); ?></span>
<?php if ($settings['show_effects'] === 'yes' && !empty($product['effects'])): ?>
<div class="cannaiq-info-item cannaiq-effects">
<strong><?php _e('Effects:', 'cannaiq-menus'); ?></strong>
<?php if (is_array($product['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['terpenes']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($settings['show_effects'] === 'yes' && !empty($product['metadata']['effects'])): ?>
<div class="crawlsy-info-item crawlsy-effects">
<strong><?php _e('Effects:', 'crawlsy-menus'); ?></strong>
<?php if (is_array($product['metadata']['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['effects']); ?></span>
<span><?php echo esc_html($product['effects']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php if ($settings['show_price'] === 'yes' && isset($product['price'])): ?>
<div class="crawlsy-single-product-price">
$<?php echo number_format($product['price'], 2); ?>
<?php if ($settings['show_price'] === 'yes' && isset($product['regular_price'])): ?>
<div class="cannaiq-single-product-price">
<?php if (!empty($product['sale_price']) && $product['sale_price'] != $product['regular_price']): ?>
<span class="cannaiq-price-sale">$<?php echo esc_html($product['sale_price']); ?></span>
<span class="cannaiq-price-regular cannaiq-strikethrough">$<?php echo esc_html($product['regular_price']); ?></span>
<?php else: ?>
$<?php echo esc_html($product['regular_price']); ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="crawlsy-out-of-stock">
<?php _e('Out of Stock', 'crawlsy-menus'); ?>
<div class="cannaiq-out-of-stock">
<?php _e('Out of Stock', 'cannaiq-menus'); ?>
</div>
<?php endif; ?>
</div>