diff --git a/backend/public/downloads/cannaiq-menus-2.0.0.zip b/backend/public/downloads/cannaiq-menus-2.0.0.zip new file mode 100644 index 00000000..5d6ee49d Binary files /dev/null and b/backend/public/downloads/cannaiq-menus-2.0.0.zip differ diff --git a/backend/public/downloads/cannaiq-menus-latest.zip b/backend/public/downloads/cannaiq-menus-latest.zip index 026c7acd..7c310084 120000 --- a/backend/public/downloads/cannaiq-menus-latest.zip +++ b/backend/public/downloads/cannaiq-menus-latest.zip @@ -1 +1 @@ -cannaiq-menus-1.7.0.zip \ No newline at end of file +cannaiq-menus-2.0.0.zip \ No newline at end of file diff --git a/wordpress-plugin/VERSION b/wordpress-plugin/VERSION index bd8bf882..227cea21 100644 --- a/wordpress-plugin/VERSION +++ b/wordpress-plugin/VERSION @@ -1 +1 @@ -1.7.0 +2.0.0 diff --git a/wordpress-plugin/assets/css/components.css b/wordpress-plugin/assets/css/components.css new file mode 100644 index 00000000..fbbf0826 --- /dev/null +++ b/wordpress-plugin/assets/css/components.css @@ -0,0 +1,740 @@ +/** + * CannaIQ Modular Components CSS + * + * Styles for the modular component library. + * Each component is independently styled and composable. + * + * @package CannaIQ_Menus + * @since 2.0.0 + */ + +/* ========================================================================== + CSS Variables (Design Tokens) + ========================================================================== */ + +:root { + /* Strain Colors */ + --cannaiq-sativa: #22c55e; + --cannaiq-indica: #8b5cf6; + --cannaiq-hybrid: #f97316; + + /* UI Colors */ + --cannaiq-discount: #ef4444; + --cannaiq-discount-bg: #fef2f2; + --cannaiq-sale: #dc2626; + --cannaiq-stock-in: #16a34a; + --cannaiq-stock-out: #9ca3af; + --cannaiq-price-original: #9ca3af; + --cannaiq-price-sale: #dc2626; + + /* Neutrals */ + --cannaiq-text-primary: #1f2937; + --cannaiq-text-secondary: #6b7280; + --cannaiq-text-muted: #9ca3af; + --cannaiq-border: #e5e7eb; + --cannaiq-bg-light: #f9fafb; + + /* Spacing */ + --cannaiq-space-xs: 0.25rem; + --cannaiq-space-sm: 0.5rem; + --cannaiq-space-md: 0.75rem; + --cannaiq-space-lg: 1rem; + --cannaiq-space-xl: 1.5rem; + + /* Border Radius */ + --cannaiq-radius-sm: 0.25rem; + --cannaiq-radius-md: 0.375rem; + --cannaiq-radius-lg: 0.5rem; + --cannaiq-radius-xl: 0.75rem; + --cannaiq-radius-full: 9999px; + + /* Shadows */ + --cannaiq-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --cannaiq-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --cannaiq-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); +} + +/* ========================================================================== + Discount Ribbon + ========================================================================== */ + +.cannaiq-discount-ribbon { + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.025em; + white-space: nowrap; +} + +/* Ribbon Style - Corner positioned */ +.cannaiq-discount-ribbon--ribbon { + position: absolute; + top: 0; + left: 0; + background: var(--cannaiq-discount); + color: white; + padding: var(--cannaiq-space-xs) var(--cannaiq-space-md); + font-size: 0.75rem; + border-bottom-right-radius: var(--cannaiq-radius-md); + z-index: 10; +} + +/* Pill Style */ +.cannaiq-discount-ribbon--pill { + background: var(--cannaiq-discount); + color: white; + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + font-size: 0.75rem; + border-radius: var(--cannaiq-radius-full); +} + +/* Text Style */ +.cannaiq-discount-ribbon--text { + color: var(--cannaiq-discount); + font-size: 0.875rem; +} + +/* Sizes */ +.cannaiq-discount-ribbon--small { + font-size: 0.625rem; + padding: 2px var(--cannaiq-space-xs); +} + +.cannaiq-discount-ribbon--large { + font-size: 0.875rem; + padding: var(--cannaiq-space-sm) var(--cannaiq-space-lg); +} + +/* ========================================================================== + Strain Badge + ========================================================================== */ + +.cannaiq-strain-badge { + display: inline-flex; + align-items: center; + gap: var(--cannaiq-space-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + border-radius: var(--cannaiq-radius-full); +} + +/* Pill Style */ +.cannaiq-strain-badge--pill { + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + font-size: 0.625rem; +} + +/* Text Style */ +.cannaiq-strain-badge--text { + padding: 0; + font-size: 0.75rem; +} + +/* Strain Type Colors */ +.cannaiq-strain-badge--sativa { + background: var(--cannaiq-sativa); + color: white; +} + +.cannaiq-strain-badge--sativa.cannaiq-strain-badge--text { + background: transparent; + color: var(--cannaiq-sativa); +} + +.cannaiq-strain-badge--indica { + background: var(--cannaiq-indica); + color: white; +} + +.cannaiq-strain-badge--indica.cannaiq-strain-badge--text { + background: transparent; + color: var(--cannaiq-indica); +} + +.cannaiq-strain-badge--hybrid { + background: var(--cannaiq-hybrid); + color: white; +} + +.cannaiq-strain-badge--hybrid.cannaiq-strain-badge--text { + background: transparent; + color: var(--cannaiq-hybrid); +} + +/* Sizes */ +.cannaiq-strain-badge--small { + font-size: 0.5rem; + padding: 2px var(--cannaiq-space-xs); +} + +.cannaiq-strain-badge--large { + font-size: 0.75rem; + padding: var(--cannaiq-space-sm) var(--cannaiq-space-md); +} + +/* ========================================================================== + THC/CBD Badge + ========================================================================== */ + +.cannaiq-potency-badge { + display: inline-flex; + align-items: center; + gap: var(--cannaiq-space-xs); + font-weight: 600; +} + +/* Badge Style */ +.cannaiq-potency-badge--badge { + background: var(--cannaiq-bg-light); + border: 1px solid var(--cannaiq-border); + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + border-radius: var(--cannaiq-radius-md); + font-size: 0.75rem; +} + +/* Pill Style */ +.cannaiq-potency-badge--pill { + background: var(--cannaiq-text-primary); + color: white; + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + border-radius: var(--cannaiq-radius-full); + font-size: 0.75rem; +} + +/* Text Style */ +.cannaiq-potency-badge--text { + color: var(--cannaiq-text-secondary); + font-size: 0.875rem; +} + +.cannaiq-potency-badge__label { + color: var(--cannaiq-text-muted); + font-size: 0.625rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.cannaiq-potency-badge__value { + font-weight: 700; +} + +/* ========================================================================== + THC/CBD Meter (Visual Progress Bar) + ========================================================================== */ + +.cannaiq-potency-meter { + display: flex; + flex-direction: column; + gap: var(--cannaiq-space-xs); +} + +.cannaiq-potency-meter__header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.75rem; +} + +.cannaiq-potency-meter__label { + font-weight: 600; + color: var(--cannaiq-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.cannaiq-potency-meter__value { + font-weight: 700; + color: var(--cannaiq-text-primary); +} + +.cannaiq-potency-meter__bar { + height: 6px; + background: var(--cannaiq-border); + border-radius: var(--cannaiq-radius-full); + overflow: hidden; +} + +.cannaiq-potency-meter__fill { + height: 100%; + border-radius: var(--cannaiq-radius-full); + transition: width 0.3s ease; +} + +.cannaiq-potency-meter--thc .cannaiq-potency-meter__fill { + background: linear-gradient(90deg, #22c55e 0%, #16a34a 100%); +} + +.cannaiq-potency-meter--cbd .cannaiq-potency-meter__fill { + background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); +} + +/* ========================================================================== + Effects Display + ========================================================================== */ + +.cannaiq-effects-container { + display: flex; + flex-wrap: wrap; + gap: var(--cannaiq-space-sm); +} + +.cannaiq-effect-chip { + display: inline-flex; + align-items: center; + gap: var(--cannaiq-space-xs); + background: color-mix(in srgb, var(--effect-color, #6b7280) 15%, white); + border: 1px solid color-mix(in srgb, var(--effect-color, #6b7280) 30%, white); + border-radius: var(--cannaiq-radius-full); + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + font-size: 0.75rem; + font-weight: 500; + color: var(--cannaiq-text-primary); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.cannaiq-effect-chip:hover { + transform: translateY(-1px); + box-shadow: var(--cannaiq-shadow-sm); +} + +.cannaiq-effect-chip svg { + flex-shrink: 0; +} + +.cannaiq-effect-chip__label { + white-space: nowrap; +} + +/* Effect Chip Sizes */ +.cannaiq-effect-chip--small { + padding: 2px var(--cannaiq-space-xs); + font-size: 0.625rem; + gap: 2px; +} + +.cannaiq-effect-chip--large { + padding: var(--cannaiq-space-sm) var(--cannaiq-space-md); + font-size: 0.875rem; +} + +/* ========================================================================== + Terpene Profile + ========================================================================== */ + +.cannaiq-terpenes { + display: flex; + flex-direction: column; + gap: var(--cannaiq-space-sm); +} + +.cannaiq-terpenes--chips { + flex-direction: row; + flex-wrap: wrap; +} + +.cannaiq-terpene-chip { + display: inline-flex; + align-items: center; + gap: var(--cannaiq-space-xs); + background: var(--cannaiq-bg-light); + border: 1px solid var(--cannaiq-border); + border-radius: var(--cannaiq-radius-full); + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + font-size: 0.75rem; +} + +.cannaiq-terpene-chip__name { + font-weight: 500; + color: var(--cannaiq-text-primary); +} + +.cannaiq-terpene-chip__percent { + color: var(--cannaiq-text-secondary); +} + +/* Terpene List Style */ +.cannaiq-terpenes--list .cannaiq-terpene-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--cannaiq-space-xs) 0; + border-bottom: 1px solid var(--cannaiq-border); +} + +.cannaiq-terpenes--list .cannaiq-terpene-item:last-child { + border-bottom: none; +} + +/* ========================================================================== + Price Block + ========================================================================== */ + +.cannaiq-price-block { + display: flex; + align-items: baseline; + gap: var(--cannaiq-space-sm); + flex-wrap: wrap; +} + +.cannaiq-price-block--stacked { + flex-direction: column; + align-items: flex-start; + gap: var(--cannaiq-space-xs); +} + +.cannaiq-price-block__original { + color: var(--cannaiq-price-original); + text-decoration: line-through; + font-size: 0.875rem; +} + +.cannaiq-price-block__sale { + color: var(--cannaiq-price-sale); + font-weight: 700; + font-size: 1.25rem; +} + +.cannaiq-price-block__regular { + color: var(--cannaiq-text-primary); + font-weight: 700; + font-size: 1.25rem; +} + +.cannaiq-price-block__weight { + color: var(--cannaiq-text-muted); + font-size: 0.875rem; +} + +/* Price Sizes */ +.cannaiq-price-block--small .cannaiq-price-block__sale, +.cannaiq-price-block--small .cannaiq-price-block__regular { + font-size: 1rem; +} + +.cannaiq-price-block--small .cannaiq-price-block__original { + font-size: 0.75rem; +} + +.cannaiq-price-block--large .cannaiq-price-block__sale, +.cannaiq-price-block--large .cannaiq-price-block__regular { + font-size: 1.5rem; +} + +/* ========================================================================== + Cart Button + ========================================================================== */ + +.cannaiq-cart-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--cannaiq-space-sm); + padding: var(--cannaiq-space-md) var(--cannaiq-space-xl); + font-weight: 600; + font-size: 0.875rem; + text-decoration: none; + border-radius: var(--cannaiq-radius-md); + cursor: pointer; + transition: all 0.15s ease; + border: 2px solid transparent; + text-transform: uppercase; + letter-spacing: 0.025em; +} + +/* Solid Style */ +.cannaiq-cart-button--solid { + background: var(--cannaiq-text-primary); + color: white; + border-color: var(--cannaiq-text-primary); +} + +.cannaiq-cart-button--solid:hover { + background: #374151; + border-color: #374151; + transform: translateY(-1px); + box-shadow: var(--cannaiq-shadow-md); +} + +/* Outline Style */ +.cannaiq-cart-button--outline { + background: transparent; + color: var(--cannaiq-text-primary); + border-color: var(--cannaiq-text-primary); +} + +.cannaiq-cart-button--outline:hover { + background: var(--cannaiq-text-primary); + color: white; +} + +/* Full Width */ +.cannaiq-cart-button--full { + width: 100%; +} + +/* Sizes */ +.cannaiq-cart-button--small { + padding: var(--cannaiq-space-sm) var(--cannaiq-space-md); + font-size: 0.75rem; +} + +.cannaiq-cart-button--large { + padding: var(--cannaiq-space-lg) var(--cannaiq-space-xl); + font-size: 1rem; +} + +/* ========================================================================== + Stock Indicator + ========================================================================== */ + +.cannaiq-stock-indicator { + display: inline-flex; + align-items: center; + gap: var(--cannaiq-space-xs); + font-size: 0.75rem; + font-weight: 500; +} + +.cannaiq-stock-indicator--in-stock { + color: var(--cannaiq-stock-in); +} + +.cannaiq-stock-indicator--out-of-stock { + color: var(--cannaiq-stock-out); +} + +/* Badge Style */ +.cannaiq-stock-indicator--badge { + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + border-radius: var(--cannaiq-radius-md); +} + +.cannaiq-stock-indicator--badge.cannaiq-stock-indicator--in-stock { + background: #dcfce7; +} + +.cannaiq-stock-indicator--badge.cannaiq-stock-indicator--out-of-stock { + background: #f3f4f6; +} + +/* Dot Indicator */ +.cannaiq-stock-indicator__dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: currentColor; +} + +/* ========================================================================== + Product Image with Overlays + ========================================================================== */ + +.cannaiq-product-image { + position: relative; + overflow: hidden; + border-radius: var(--cannaiq-radius-lg); + background: var(--cannaiq-bg-light); +} + +.cannaiq-product-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.cannaiq-product-image:hover img { + transform: scale(1.05); +} + +.cannaiq-product-image__overlay { + position: absolute; + padding: var(--cannaiq-space-sm); +} + +.cannaiq-product-image__overlay--top-left { + top: 0; + left: 0; +} + +.cannaiq-product-image__overlay--top-right { + top: 0; + right: 0; +} + +.cannaiq-product-image__overlay--bottom-left { + bottom: 0; + left: 0; +} + +.cannaiq-product-image__overlay--bottom-right { + bottom: 0; + right: 0; +} + +/* Badge Stack in Overlays */ +.cannaiq-product-image__badges { + display: flex; + gap: var(--cannaiq-space-xs); + flex-wrap: wrap; +} + +/* ========================================================================== + Weight Options Selector + ========================================================================== */ + +.cannaiq-weight-options { + display: flex; + gap: var(--cannaiq-space-xs); + flex-wrap: wrap; +} + +.cannaiq-weight-option { + padding: var(--cannaiq-space-xs) var(--cannaiq-space-sm); + border: 1px solid var(--cannaiq-border); + border-radius: var(--cannaiq-radius-md); + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + background: white; +} + +.cannaiq-weight-option:hover { + border-color: var(--cannaiq-text-primary); +} + +.cannaiq-weight-option--selected { + background: var(--cannaiq-text-primary); + color: white; + border-color: var(--cannaiq-text-primary); +} + +.cannaiq-weight-option__price { + color: var(--cannaiq-text-secondary); + margin-left: var(--cannaiq-space-xs); +} + +.cannaiq-weight-option--selected .cannaiq-weight-option__price { + color: rgba(255, 255, 255, 0.8); +} + +/* Dropdown Style */ +.cannaiq-weight-options--dropdown { + display: block; +} + +.cannaiq-weight-options--dropdown select { + width: 100%; + padding: var(--cannaiq-space-sm) var(--cannaiq-space-md); + border: 1px solid var(--cannaiq-border); + border-radius: var(--cannaiq-radius-md); + font-size: 0.875rem; + background: white; + cursor: pointer; +} + +/* ========================================================================== + Card Container (for premade templates) + ========================================================================== */ + +.cannaiq-product-card { + display: flex; + flex-direction: column; + background: white; + border-radius: var(--cannaiq-radius-xl); + overflow: hidden; + box-shadow: var(--cannaiq-shadow-sm); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.cannaiq-product-card:hover { + box-shadow: var(--cannaiq-shadow-lg); + transform: translateY(-2px); +} + +.cannaiq-product-card__image { + position: relative; + aspect-ratio: 1; + overflow: hidden; +} + +.cannaiq-product-card__body { + padding: var(--cannaiq-space-lg); + display: flex; + flex-direction: column; + gap: var(--cannaiq-space-sm); + flex: 1; +} + +.cannaiq-product-card__title { + font-size: 1rem; + font-weight: 700; + color: var(--cannaiq-text-primary); + margin: 0; + line-height: 1.3; +} + +.cannaiq-product-card__brand { + font-size: 0.875rem; + color: var(--cannaiq-text-secondary); + margin: 0; +} + +.cannaiq-product-card__footer { + margin-top: auto; + padding-top: var(--cannaiq-space-md); +} + +/* ========================================================================== + Utility Classes + ========================================================================== */ + +.cannaiq-flex { + display: flex; +} + +.cannaiq-flex-wrap { + flex-wrap: wrap; +} + +.cannaiq-items-center { + align-items: center; +} + +.cannaiq-justify-between { + justify-content: space-between; +} + +.cannaiq-gap-xs { + gap: var(--cannaiq-space-xs); +} + +.cannaiq-gap-sm { + gap: var(--cannaiq-space-sm); +} + +.cannaiq-gap-md { + gap: var(--cannaiq-space-md); +} + +.cannaiq-gap-lg { + gap: var(--cannaiq-space-lg); +} + +.cannaiq-mt-auto { + margin-top: auto; +} + +.cannaiq-text-center { + text-align: center; +} + +.cannaiq-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/wordpress-plugin/cannaiq-menus.php b/wordpress-plugin/cannaiq-menus.php index 92ca4579..b01f46fc 100644 --- a/wordpress-plugin/cannaiq-menus.php +++ b/wordpress-plugin/cannaiq-menus.php @@ -1,10 +1,10 @@ add_category( 'cannaiq', [ - 'title' => __('CannaIQ', 'cannaiq-menus'), + 'title' => __('CannaiQ', 'cannaiq-menus'), 'icon' => 'fa fa-cannabis', ] ); @@ -60,9 +60,13 @@ class CannaIQ_Menus_Plugin { // 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 @@ -78,6 +82,7 @@ class CannaIQ_Menus_Plugin { * 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'; @@ -85,18 +90,46 @@ class CannaIQ_Menus_Plugin { 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', @@ -104,6 +137,14 @@ class CannaIQ_Menus_Plugin { 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', @@ -118,8 +159,8 @@ class CannaIQ_Menus_Plugin { */ public function add_admin_menu() { add_menu_page( - 'CannaIQ Menus', - 'CannaIQ Menus', + 'CannaiQ Menus', + 'CannaiQ Menus', 'manage_options', 'cannaiq-menus', [$this, 'admin_page'], @@ -147,9 +188,9 @@ class CannaIQ_Menus_Plugin { public function admin_page() { ?>
-

CannaIQ Menus Settings

-

Version by CannaIQ

-

Display real-time cannabis menus with data updated daily from CannaIQ.

+

CannaiQ Menus Settings

+

Version by CannaiQ

+

Display real-time cannabis menus with data updated daily from CannaiQ.

@@ -162,7 +203,7 @@ class CannaIQ_Menus_Plugin { -

Your authentication token from the CannaIQ admin dashboard. The token includes your store configuration.

+

Your authentication token from the CannaiQ admin dashboard. The token includes your store configuration.

@@ -300,10 +341,10 @@ class CannaIQ_Menus_Plugin {

Elementor Widgets

-

If you have Elementor installed, you can use the CannaIQ widgets:

+

If you have Elementor installed, you can use the CannaiQ widgets:

16, + 'class' => '', + 'color' => 'currentColor', + ]; + $args = wp_parse_args($args, $defaults); + + $effect_key = strtolower(trim($effect)); + $icons = cannaiq_get_effect_icons(); + + if (!isset($icons[$effect_key])) { + return ''; + } + + $svg = $icons[$effect_key]; + $size = intval($args['size']); + $class = esc_attr($args['class']); + $color = esc_attr($args['color']); + + // Replace placeholders in SVG + $svg = str_replace( + ['{SIZE}', '{CLASS}', '{COLOR}'], + [$size, $class, $color], + $svg + ); + + return $svg; +} + +/** + * Get all effect icons + * + * @return array Associative array of effect => SVG + */ +function cannaiq_get_effect_icons() { + return [ + 'happy' => '', + + 'relaxed' => '', + + 'sleepy' => '', + + 'euphoric' => '', + + 'creative' => '', + + 'energetic' => '', + + 'focused' => '', + + 'hungry' => '', + + 'uplifted' => '', + + 'talkative' => '', + + 'giggly' => '', + + 'aroused' => '', + + 'tingly' => '', + + 'calm' => '', + + 'sedated' => '', + ]; +} + +/** + * Get effect color + * + * @param string $effect Effect name + * @return string Hex color code + */ +function cannaiq_get_effect_color($effect) { + $colors = [ + 'happy' => '#FFD700', // Gold + 'relaxed' => '#87CEEB', // Sky blue + 'sleepy' => '#9370DB', // Medium purple + 'euphoric' => '#FF69B4', // Hot pink + 'creative' => '#FF8C00', // Dark orange + 'energetic' => '#32CD32', // Lime green + 'focused' => '#4169E1', // Royal blue + 'hungry' => '#FF6347', // Tomato + 'uplifted' => '#00CED1', // Dark turquoise + 'talkative' => '#DDA0DD', // Plum + 'giggly' => '#FFB6C1', // Light pink + 'aroused' => '#DC143C', // Crimson + 'tingly' => '#8A2BE2', // Blue violet + 'calm' => '#98FB98', // Pale green + 'sedated' => '#708090', // Slate gray + ]; + + $key = strtolower(trim($effect)); + return isset($colors[$key]) ? $colors[$key] : '#6B7280'; // Default gray +} + +/** + * Render effect chip HTML + * + * @param string $effect Effect name + * @param array $args Optional args: show_icon, size, class + * @return string HTML for effect chip + */ +function cannaiq_render_effect_chip($effect, $args = []) { + $defaults = [ + 'show_icon' => true, + 'size' => 'medium', + 'class' => '', + ]; + $args = wp_parse_args($args, $defaults); + + $effect_name = ucfirst(strtolower(trim($effect))); + $color = cannaiq_get_effect_color($effect); + $size_class = 'cannaiq-effect-chip--' . esc_attr($args['size']); + $extra_class = esc_attr($args['class']); + + $icon_html = ''; + if ($args['show_icon']) { + $icon_html = cannaiq_get_effect_icon($effect, [ + 'size' => $args['size'] === 'small' ? 12 : ($args['size'] === 'large' ? 20 : 16), + 'color' => $color, + ]); + } + + return sprintf( + '%s%s', + $size_class, + $extra_class, + esc_attr($color), + $icon_html, + esc_html($effect_name) + ); +} + +/** + * Render multiple effect chips + * + * @param array $effects Array of effect names + * @param array $args Optional args: limit, show_icon, size + * @return string HTML for all effect chips + */ +function cannaiq_render_effects($effects, $args = []) { + $defaults = [ + 'limit' => 3, + 'show_icon' => true, + 'size' => 'medium', + 'class' => '', + ]; + $args = wp_parse_args($args, $defaults); + + if (!is_array($effects)) { + return ''; + } + + $effects = array_slice($effects, 0, intval($args['limit'])); + $chips = array_map(function($effect) use ($args) { + return cannaiq_render_effect_chip($effect, [ + 'show_icon' => $args['show_icon'], + 'size' => $args['size'], + ]); + }, $effects); + + return sprintf( + '
%s
', + esc_attr($args['class']), + implode('', $chips) + ); +} diff --git a/wordpress-plugin/widgets/brand-grid.php b/wordpress-plugin/widgets/brand-grid.php index d16dbe74..9acc722f 100644 --- a/wordpress-plugin/widgets/brand-grid.php +++ b/wordpress-plugin/widgets/brand-grid.php @@ -14,7 +14,7 @@ class CannaIQ_Menus_Brand_Grid_Widget extends \Elementor\Widget_Base { } public function get_title() { - return __('CannaIQ Brand Grid', 'cannaiq-menus'); + return __('CannaiQ Brand Grid', 'cannaiq-menus'); } public function get_icon() { diff --git a/wordpress-plugin/widgets/card-template-premium.php b/wordpress-plugin/widgets/card-template-premium.php new file mode 100644 index 00000000..63e2881e --- /dev/null +++ b/wordpress-plugin/widgets/card-template-premium.php @@ -0,0 +1,510 @@ +start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'content_note', + [ + 'type' => \Elementor\Controls_Manager::RAW_HTML, + 'raw' => __('This card uses the current product context from Product Loop or Product Grid. Place it inside a CannaiQ Product Loop widget.', 'cannaiq-menus'), + 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', + ] + ); + + $this->end_controls_section(); + + // Components Section + $this->start_controls_section( + 'components_section', + [ + 'label' => __('Components', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'show_discount', + [ + 'label' => __('Show Discount Ribbon', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_image', + [ + 'label' => __('Show Product Image', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_strain_badge', + [ + 'label' => __('Show Strain Badge', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_thc_badge', + [ + 'label' => __('Show THC Badge', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_name', + [ + 'label' => __('Show Product Name', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_brand', + [ + 'label' => __('Show Brand', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_effects', + [ + 'label' => __('Show Effects', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'effects_limit', + [ + 'label' => __('Effects Limit', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 3, + 'min' => 1, + 'max' => 5, + 'condition' => [ + 'show_effects' => 'yes', + ], + ] + ); + + $this->add_control( + 'show_price', + [ + 'label' => __('Show Price', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_weight', + [ + 'label' => __('Show Weight', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_cart_button', + [ + 'label' => __('Show Cart Button', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'button_text', + [ + 'label' => __('Button Text', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => 'ADD TO CART', + 'condition' => [ + 'show_cart_button' => 'yes', + ], + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'card_style_section', + [ + 'label' => __('Card Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'card_background', + [ + 'label' => __('Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ffffff', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-card' => 'background-color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'card_border_radius', + [ + 'label' => __('Border Radius', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 30, + ], + ], + 'default' => [ + 'size' => 12, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-card' => 'border-radius: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_control( + 'card_padding', + [ + 'label' => __('Padding', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 40, + ], + ], + 'default' => [ + 'size' => 16, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-card__body' => 'padding: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Box_Shadow::get_type(), + [ + 'name' => 'card_shadow', + 'selector' => '{{WRAPPER}} .cannaiq-product-card', + ] + ); + + $this->end_controls_section(); + + // Typography Section + $this->start_controls_section( + 'typography_section', + [ + 'label' => __('Typography', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'title_typography', + 'label' => __('Title', 'cannaiq-menus'), + 'selector' => '{{WRAPPER}} .cannaiq-product-card__title', + ] + ); + + $this->add_control( + 'title_color', + [ + 'label' => __('Title Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-card__title' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'brand_color', + [ + 'label' => __('Brand Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#6b7280', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-card__brand' => 'color: {{VALUE}};', + ], + ] + ); + + $this->end_controls_section(); + + // Button Style Section + $this->start_controls_section( + 'button_style_section', + [ + 'label' => __('Button Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + 'condition' => [ + 'show_cart_button' => 'yes', + ], + ] + ); + + $this->add_control( + 'button_background', + [ + 'label' => __('Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button' => 'background-color: {{VALUE}}; border-color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'button_text_color', + [ + 'label' => __('Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ffffff', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'button_hover_background', + [ + 'label' => __('Hover Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#374151', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button:hover' => 'background-color: {{VALUE}}; border-color: {{VALUE}};', + ], + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + global $cannaiq_current_product; + $product = $cannaiq_current_product ?? []; + + if (empty($product)) { + echo '

' . __('No product context available. Place this widget inside a Product Loop.', 'cannaiq-menus') . '

'; + return; + } + + // Extract product data + $name = $product['Name'] ?? $product['name'] ?? ''; + $brand = $product['brand']['name'] ?? $product['brandName'] ?? $product['brand'] ?? ''; + $image_url = $product['Image'] ?? $product['images'][0]['url'] ?? $product['image_url'] ?? ''; + $strain_type = strtolower($product['strainType'] ?? $product['strain_type'] ?? ''); + $thc = $product['THCContent']['range'][0] ?? $product['THC'] ?? $product['thc_percentage'] ?? null; + $weight = $product['Options'][0] ?? $product['rawOptions'][0] ?? $product['weight'] ?? ''; + $menu_url = $product['menuUrl'] ?? $product['menu_url'] ?? $product['productUrl'] ?? '#'; + + // Price + $original_price = $product['Prices'][0] ?? $product['regular_price'] ?? null; + $sale_price = $product['specialPrice'] ?? $product['sale_price'] ?? null; + $is_on_sale = $sale_price && $sale_price > 0 && $sale_price < $original_price; + $discount_percent = 0; + if ($is_on_sale && $original_price > 0) { + $discount_percent = round((($original_price - $sale_price) / $original_price) * 100); + } + + // Effects + $effects = $product['effects'] ?? []; + if (!empty($effects) && !isset($effects[0])) { + arsort($effects); + $effects = array_keys($effects); + } + $effects_limit = intval($settings['effects_limit']) ?: 3; + $effects = array_slice($effects, 0, $effects_limit); + + // Strain colors + $strain_colors = [ + 'sativa' => '#22c55e', + 'indica' => '#8b5cf6', + 'hybrid' => '#f97316', + ]; + + ?> +
+ + +
+
+ + <?php echo esc_attr($name); ?> + + + + 0): ?> +
+ % OFF +
+ + + + 0)): ?> +
+
+ + + + 0): ?> + % THC + +
+
+ +
+
+ + + +
+ +

+ + + +

by

+ + + +
+ $effects_limit, 'show_icon' => true, 'size' => 'small']); ?> +
+ + + + +
+
+ start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'button_text', + [ + 'label' => __('Button Text', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => 'ADD TO CART', + ] + ); + + $this->add_control( + 'link_source', + [ + 'label' => __('Link Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom URL', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_url', + [ + 'label' => __('Custom URL', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::URL, + 'placeholder' => 'https://dutchie.com/store/...', + 'condition' => [ + 'link_source' => 'custom', + ], + ] + ); + + $this->add_control( + 'open_in_new_tab', + [ + 'label' => __('Open in New Tab', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_icon', + [ + 'label' => __('Show Icon', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + ] + ); + + $this->add_control( + 'icon_position', + [ + 'label' => __('Icon Position', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'after', + 'options' => [ + 'before' => __('Before Text', 'cannaiq-menus'), + 'after' => __('After Text', 'cannaiq-menus'), + ], + 'condition' => [ + 'show_icon' => 'yes', + ], + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'button_style', + [ + 'label' => __('Button Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'solid', + 'options' => [ + 'solid' => __('Solid', 'cannaiq-menus'), + 'outline' => __('Outline', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'full_width', + [ + 'label' => __('Full Width', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + ] + ); + + $this->add_control( + 'size', + [ + 'label' => __('Size', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'medium', + 'options' => [ + 'small' => __('Small', 'cannaiq-menus'), + 'medium' => __('Medium', 'cannaiq-menus'), + 'large' => __('Large', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'background_color', + [ + 'label' => __('Background Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button--solid' => 'background-color: {{VALUE}}; border-color: {{VALUE}};', + '{{WRAPPER}} .cannaiq-cart-button--outline' => 'border-color: {{VALUE}};', + '{{WRAPPER}} .cannaiq-cart-button--outline:hover' => 'background-color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'text_color', + [ + 'label' => __('Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ffffff', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button--solid' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'outline_text_color', + [ + 'label' => __('Outline Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button--outline' => 'color: {{VALUE}};', + ], + 'condition' => [ + 'button_style' => 'outline', + ], + ] + ); + + $this->add_control( + 'hover_background_color', + [ + 'label' => __('Hover Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#374151', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button--solid:hover' => 'background-color: {{VALUE}}; border-color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'border_radius', + [ + 'label' => __('Border Radius', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 50, + ], + ], + 'default' => [ + 'size' => 6, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-cart-button' => 'border-radius: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-cart-button', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get URL + $url = '#'; + if ($settings['link_source'] === 'custom' && !empty($settings['custom_url']['url'])) { + $url = $settings['custom_url']['url']; + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $url = $cannaiq_current_product['menuUrl'] + ?? $cannaiq_current_product['menu_url'] + ?? $cannaiq_current_product['productUrl'] + ?? '#'; + } + } + + // Build classes + $classes = [ + 'cannaiq-cart-button', + 'cannaiq-cart-button--' . $settings['button_style'], + ]; + if ($settings['full_width'] === 'yes') { + $classes[] = 'cannaiq-cart-button--full'; + } + if ($settings['size'] !== 'medium') { + $classes[] = 'cannaiq-cart-button--' . $settings['size']; + } + + // Target attribute + $target = $settings['open_in_new_tab'] === 'yes' ? ' target="_blank" rel="noopener noreferrer"' : ''; + + // Icon SVG (arrow right) + $icon = ''; + + ?> + > + + + + + + + + + start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Discount Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom value', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_discount', + [ + 'label' => __('Discount Percentage', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 25, + 'min' => 1, + 'max' => 99, + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'format', + [ + 'label' => __('Display Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'ribbon', + 'options' => [ + 'ribbon' => __('Ribbon', 'cannaiq-menus'), + 'pill' => __('Pill', 'cannaiq-menus'), + 'text' => __('Text Only', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'text_template', + [ + 'label' => __('Text Template', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => '{percent}% OFF', + 'description' => __('Use {percent} as placeholder', 'cannaiq-menus'), + ] + ); + + $this->add_control( + 'hide_if_no_discount', + [ + 'label' => __('Hide if No Discount', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'size', + [ + 'label' => __('Size', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'medium', + 'options' => [ + 'small' => __('Small', 'cannaiq-menus'), + 'medium' => __('Medium', 'cannaiq-menus'), + 'large' => __('Large', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'background_color', + [ + 'label' => __('Background Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ef4444', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-discount-ribbon' => 'background-color: {{VALUE}};', + ], + 'condition' => [ + 'format!' => 'text', + ], + ] + ); + + $this->add_control( + 'text_color', + [ + 'label' => __('Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ffffff', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-discount-ribbon' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-discount-ribbon', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get discount percentage + $discount = 0; + if ($settings['source'] === 'custom') { + $discount = intval($settings['custom_discount']); + } else { + // Get from product context + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $original = $cannaiq_current_product['Prices'][0] ?? $cannaiq_current_product['regular_price'] ?? null; + $sale = $cannaiq_current_product['specialPrice'] ?? $cannaiq_current_product['sale_price'] ?? null; + + if ($original && $sale && $original > $sale) { + $discount = round((($original - $sale) / $original) * 100); + } + } + } + + // Hide if no discount and setting enabled + if ($discount <= 0 && $settings['hide_if_no_discount'] === 'yes') { + return; + } + + // Build display text + $text = str_replace('{percent}', $discount, $settings['text_template']); + + // Build classes + $classes = [ + 'cannaiq-discount-ribbon', + 'cannaiq-discount-ribbon--' . $settings['format'], + ]; + if ($settings['size'] !== 'medium') { + $classes[] = 'cannaiq-discount-ribbon--' . $settings['size']; + } + + printf( + '%s', + esc_attr(implode(' ', $classes)), + esc_html($text) + ); + } +} diff --git a/wordpress-plugin/widgets/dynamic-tags-extended.php b/wordpress-plugin/widgets/dynamic-tags-extended.php new file mode 100644 index 00000000..f30b7d37 --- /dev/null +++ b/wordpress-plugin/widgets/dynamic-tags-extended.php @@ -0,0 +1,793 @@ +register(new CannaIQ_Discount_Percent_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Discount_Badge_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Strain_Badge_Tag()); + $dynamic_tags_manager->register(new CannaIQ_THC_Badge_Tag()); + $dynamic_tags_manager->register(new CannaIQ_CBD_Badge_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Effects_Chips_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Single_Effect_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Terpenes_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Price_Display_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Sale_Price_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Original_Price_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Menu_URL_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Subcategory_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Stock_Quantity_Tag()); + $dynamic_tags_manager->register(new CannaIQ_Stock_Status_Tag()); +}, 20); // Priority 20 to run after base tags + +/** + * Discount Percentage Tag + */ +class CannaIQ_Discount_Percent_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-discount-percent'; + } + + public function get_title() { + return __('Discount %', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('format', [ + 'label' => __('Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'off', + 'options' => [ + 'off' => 'XX% OFF', + 'percent' => 'XX%', + 'number' => 'XX', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $format = $this->get_settings('format'); + + $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); + + switch ($format) { + case 'off': + echo esc_html($percent . '% OFF'); + break; + case 'percent': + echo esc_html($percent . '%'); + break; + case 'number': + echo esc_html($percent); + break; + } + } +} + +/** + * Discount Badge Tag (HTML) + */ +class CannaIQ_Discount_Badge_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-discount-badge'; + } + + public function get_title() { + return __('Discount Badge', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('style', [ + 'label' => __('Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'ribbon', + 'options' => [ + 'ribbon' => 'Ribbon', + 'pill' => 'Pill', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $style = $this->get_settings('style'); + + $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--' . $style; + + printf( + '%s%% OFF', + esc_attr($class), + esc_html($percent) + ); + } +} + +/** + * Strain Badge Tag (HTML) + */ +class CannaIQ_Strain_Badge_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-strain-badge'; + } + + public function get_title() { + return __('Strain Badge', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('style', [ + 'label' => __('Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'pill', + 'options' => [ + 'pill' => 'Pill', + 'text' => 'Text Only', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $style = $this->get_settings('style'); + + $strain = strtolower($product['strainType'] ?? $product['strain_type'] ?? ''); + if (empty($strain) || !in_array($strain, ['sativa', 'indica', 'hybrid'])) { + return; + } + + $colors = [ + 'sativa' => '#22c55e', + 'indica' => '#8b5cf6', + 'hybrid' => '#f97316', + ]; + $color = $colors[$strain]; + + $class = 'cannaiq-strain-badge cannaiq-strain-badge--' . $style . ' cannaiq-strain-badge--' . $strain; + $css_style = $style === 'pill' + ? sprintf('background-color: %s; color: white;', $color) + : sprintf('color: %s;', $color); + + printf( + '%s', + esc_attr($class), + esc_attr($css_style), + esc_html(strtoupper($strain)) + ); + } +} + +/** + * THC Badge Tag (HTML) + */ +class CannaIQ_THC_Badge_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-thc-badge'; + } + + public function get_title() { + return __('THC Badge', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('style', [ + 'label' => __('Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'badge', + 'options' => [ + 'badge' => 'Badge', + 'pill' => 'Pill', + 'text' => 'Text Only', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $style = $this->get_settings('style'); + + $thc = $product['THCContent']['range'][0] + ?? $product['THC'] + ?? $product['thc_percentage'] + ?? null; + + if (!$thc || $thc <= 0) { + return; + } + + $class = 'cannaiq-potency-badge cannaiq-potency-badge--' . $style; + $formatted = number_format((float)$thc, 1) . '% THC'; + + printf( + '%s', + esc_attr($class), + esc_html($formatted) + ); + } +} + +/** + * CBD Badge Tag (HTML) + */ +class CannaIQ_CBD_Badge_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-cbd-badge'; + } + + public function get_title() { + return __('CBD Badge', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('style', [ + 'label' => __('Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'badge', + 'options' => [ + 'badge' => 'Badge', + 'pill' => 'Pill', + 'text' => 'Text Only', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $style = $this->get_settings('style'); + + $cbd = $product['CBDContent']['range'][0] + ?? $product['CBD'] + ?? $product['cbd_percentage'] + ?? null; + + if (!$cbd || $cbd <= 0) { + return; + } + + $class = 'cannaiq-potency-badge cannaiq-potency-badge--' . $style; + $formatted = number_format((float)$cbd, 1) . '% CBD'; + + printf( + '%s', + esc_attr($class), + esc_html($formatted) + ); + } +} + +/** + * Effects Chips Tag (HTML) + */ +class CannaIQ_Effects_Chips_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-effects-chips'; + } + + public function get_title() { + return __('Effects Chips', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('limit', [ + 'label' => __('Max Effects', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 3, + 'min' => 1, + 'max' => 10, + ]); + + $this->add_control('show_icons', [ + 'label' => __('Show Icons', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ]); + } + + public function render() { + $product = $this->get_current_product(); + $limit = (int)$this->get_settings('limit') ?: 3; + $show_icons = $this->get_settings('show_icons') === 'yes'; + + $effects = $product['effects'] ?? []; + if (empty($effects) || !is_array($effects)) { + return; + } + + // If associative array with scores, sort by score + if (!isset($effects[0])) { + arsort($effects); + $effects = array_keys($effects); + } + + $effects = array_slice($effects, 0, $limit); + + echo cannaiq_render_effects($effects, [ + 'limit' => $limit, + 'show_icon' => $show_icons, + 'size' => 'medium', + ]); + } +} + +/** + * Single Effect Tag + */ +class CannaIQ_Single_Effect_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-single-effect'; + } + + public function get_title() { + return __('Single Effect', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('effect_index', [ + 'label' => __('Effect Index', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 1, + 'min' => 1, + 'max' => 10, + 'description' => __('1 = first effect, 2 = second, etc.', 'cannaiq-menus'), + ]); + + $this->add_control('show_icon', [ + 'label' => __('Show Icon', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ]); + } + + public function render() { + $product = $this->get_current_product(); + $index = (int)$this->get_settings('effect_index') - 1; // Convert to 0-based + $show_icon = $this->get_settings('show_icon') === 'yes'; + + $effects = $product['effects'] ?? []; + if (empty($effects) || !is_array($effects)) { + return; + } + + // If associative array with scores, sort by score and get keys + if (!isset($effects[0])) { + arsort($effects); + $effects = array_keys($effects); + } + + if (!isset($effects[$index])) { + return; + } + + $effect = $effects[$index]; + echo cannaiq_render_effect_chip($effect, [ + 'show_icon' => $show_icon, + 'size' => 'medium', + ]); + } +} + +/** + * Terpenes Tag (HTML) + */ +class CannaIQ_Terpenes_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-terpenes'; + } + + public function get_title() { + return __('Terpenes', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('format', [ + 'label' => __('Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'chips', + 'options' => [ + 'chips' => 'Chips', + 'list' => 'List', + 'text' => 'Text', + ], + ]); + + $this->add_control('limit', [ + 'label' => __('Max Terpenes', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 3, + 'min' => 1, + 'max' => 10, + ]); + } + + public function render() { + $product = $this->get_current_product(); + $format = $this->get_settings('format'); + $limit = (int)$this->get_settings('limit') ?: 3; + + $terpenes = $product['terpenes'] ?? []; + if (empty($terpenes) || !is_array($terpenes)) { + return; + } + + $terpenes = array_slice($terpenes, 0, $limit); + + switch ($format) { + case 'chips': + echo '
'; + foreach ($terpenes as $terp) { + $name = $terp['name'] ?? ''; + $percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : ''; + printf( + '%s%s', + esc_html($name), + esc_html($percent) + ); + } + echo '
'; + break; + + case 'list': + echo '
'; + foreach ($terpenes as $terp) { + $name = $terp['name'] ?? ''; + $percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : ''; + printf( + '
%s%s
', + esc_html($name), + esc_html($percent) + ); + } + echo '
'; + break; + + case '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)); + break; + } + } +} + +/** + * Price Display Tag (with sale handling) + */ +class CannaIQ_Price_Display_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-price-display'; + } + + public function get_title() { + return __('Price Display', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('show_original', [ + 'label' => __('Show Original on Sale', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ]); + } + + public function render() { + $product = $this->get_current_product(); + $show_original = $this->get_settings('show_original') === 'yes'; + + $original = $product['Prices'][0] ?? $product['regular_price'] ?? null; + $sale = $product['specialPrice'] ?? $product['sale_price'] ?? null; + + if (!$original || $original <= 0) { + return; + } + + $is_on_sale = $sale && $sale > 0 && $sale < $original; + + echo ''; + if ($is_on_sale) { + if ($show_original) { + printf( + '$%s', + esc_html(number_format((float)$original, 2)) + ); + } + printf( + '$%s', + esc_html(number_format((float)$sale, 2)) + ); + } else { + printf( + '$%s', + esc_html(number_format((float)$original, 2)) + ); + } + echo ''; + } +} + +/** + * Sale Price Tag + */ +class CannaIQ_Sale_Price_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-price-sale'; + } + + public function get_title() { + return __('Sale Price', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + public function render() { + $product = $this->get_current_product(); + + $sale = $product['specialPrice'] ?? $product['sale_price'] ?? null; + + if ($sale && $sale > 0) { + echo '$' . number_format((float)$sale, 2); + } + } +} + +/** + * Original Price Tag + */ +class CannaIQ_Original_Price_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-price-original'; + } + + public function get_title() { + return __('Original Price', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + public function render() { + $product = $this->get_current_product(); + + $original = $product['Prices'][0] ?? $product['regular_price'] ?? null; + + if ($original && $original > 0) { + echo '$' . number_format((float)$original, 2); + } + } +} + +/** + * Menu URL Tag + */ +class CannaIQ_Menu_URL_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-menu-url'; + } + + public function get_title() { + return __('Menu URL', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::URL_CATEGORY]; + } + + public function render() { + $product = $this->get_current_product(); + + $url = $product['menuUrl'] + ?? $product['menu_url'] + ?? $product['productUrl'] + ?? ''; + + echo esc_url($url); + } +} + +/** + * Subcategory Tag + */ +class CannaIQ_Subcategory_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-subcategory'; + } + + public function get_title() { + return __('Subcategory', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + public function render() { + $product = $this->get_current_product(); + + $subcategory = $product['subcategory'] + ?? $product['subCategory'] + ?? ''; + + echo esc_html($subcategory); + } +} + +/** + * Stock Quantity Tag + */ +class CannaIQ_Stock_Quantity_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-stock-qty'; + } + + public function get_title() { + return __('Stock Quantity', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + public function render() { + $product = $this->get_current_product(); + + $qty = $product['POSMetaData']['children'][0]['quantity'] + ?? $product['quantity'] + ?? null; + + if ($qty !== null) { + echo (int)$qty; + } + } +} + +/** + * Stock Status Tag (HTML badge) + */ +class CannaIQ_Stock_Status_Tag extends CannaIQ_Dynamic_Tag_Base { + + public function get_name() { + return 'cannaiq-stock-status'; + } + + public function get_title() { + return __('Stock Status Badge', 'cannaiq-menus'); + } + + public function get_categories() { + return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; + } + + protected function register_controls() { + $this->add_control('style', [ + 'label' => __('Style', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'badge', + 'options' => [ + 'badge' => 'Badge', + 'text' => 'Text', + 'dot' => 'Dot + Text', + ], + ]); + } + + public function render() { + $product = $this->get_current_product(); + $style = $this->get_settings('style'); + + $status = $product['Status'] ?? ''; + $in_stock = ($status === 'Active' || $status === 'In Stock' || !empty($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 ($style === 'badge') { + $class .= ' cannaiq-stock-indicator--badge'; + } + + printf('', esc_attr($class)); + + if ($style === 'dot') { + echo ''; + } + + echo esc_html($text); + echo ''; + } +} diff --git a/wordpress-plugin/widgets/dynamic-tags.php b/wordpress-plugin/widgets/dynamic-tags.php index 305f2680..641de373 100644 --- a/wordpress-plugin/widgets/dynamic-tags.php +++ b/wordpress-plugin/widgets/dynamic-tags.php @@ -17,7 +17,7 @@ add_action('elementor/dynamic_tags/register', function($dynamic_tags_manager) { // Register CannaIQ group $dynamic_tags_manager->register_group('cannaiq', [ - 'title' => __('CannaIQ Product', 'cannaiq-menus') + 'title' => __('CannaiQ Product', 'cannaiq-menus') ]); // Register all tags diff --git a/wordpress-plugin/widgets/effects-display.php b/wordpress-plugin/widgets/effects-display.php new file mode 100644 index 00000000..781d9f30 --- /dev/null +++ b/wordpress-plugin/widgets/effects-display.php @@ -0,0 +1,288 @@ +start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Effects Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom values', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_effects', + [ + 'label' => __('Custom Effects', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => 'Happy, Relaxed, Creative', + 'description' => __('Comma-separated list of effects', 'cannaiq-menus'), + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'limit', + [ + 'label' => __('Max Effects', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 3, + 'min' => 1, + 'max' => 10, + ] + ); + + $this->add_control( + 'show_icons', + [ + 'label' => __('Show Icons', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'use_colors', + [ + 'label' => __('Colored Chips', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + 'description' => __('Use effect-specific colors', 'cannaiq-menus'), + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'size', + [ + 'label' => __('Size', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'medium', + 'options' => [ + 'small' => __('Small', 'cannaiq-menus'), + 'medium' => __('Medium', 'cannaiq-menus'), + 'large' => __('Large', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'gap', + [ + 'label' => __('Gap', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 2, + 'max' => 20, + ], + ], + 'default' => [ + 'size' => 8, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-effects-container' => 'gap: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_control( + 'default_background', + [ + 'label' => __('Default Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#f3f4f6', + 'condition' => [ + 'use_colors!' => 'yes', + ], + ] + ); + + $this->add_control( + 'text_color', + [ + 'label' => __('Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-effect-chip' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-effect-chip', + ] + ); + + $this->add_control( + 'border_radius', + [ + 'label' => __('Border Radius', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 50, + ], + ], + 'default' => [ + 'size' => 999, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-effect-chip' => 'border-radius: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get effects + $effects = []; + if ($settings['source'] === 'custom') { + $effects_string = $settings['custom_effects']; + $effects = array_map('trim', explode(',', $effects_string)); + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $raw_effects = $cannaiq_current_product['effects'] ?? []; + if (is_array($raw_effects)) { + // If effects is associative array with scores, sort by score + if (isset($raw_effects[0]) && !is_array($raw_effects[0])) { + $effects = $raw_effects; + } else { + // Sort by value descending and get keys + arsort($raw_effects); + $effects = array_keys($raw_effects); + } + } + } + } + + if (empty($effects)) { + return; + } + + // Apply limit + $limit = intval($settings['limit']); + $effects = array_slice($effects, 0, $limit); + + // Determine icon size + $icon_size = $settings['size'] === 'small' ? 12 : ($settings['size'] === 'large' ? 20 : 16); + ?> +
+ + + + + $icon_size, + 'color' => $settings['use_colors'] === 'yes' ? $color : 'currentColor', + ]); ?> + + + + +
+ start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Price Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom values', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_price', + [ + 'label' => __('Regular Price', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 45, + 'min' => 0, + 'step' => 0.01, + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'custom_sale_price', + [ + 'label' => __('Sale Price (optional)', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => '', + 'min' => 0, + 'step' => 0.01, + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'show_original_when_sale', + [ + 'label' => __('Show Original Price on Sale', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'show_weight', + [ + 'label' => __('Show Weight', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'custom_weight', + [ + 'label' => __('Weight Text', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => '1/8 oz', + 'condition' => [ + 'source' => 'custom', + 'show_weight' => 'yes', + ], + ] + ); + + $this->add_control( + 'layout', + [ + 'label' => __('Layout', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'inline', + 'options' => [ + 'inline' => __('Inline', 'cannaiq-menus'), + 'stacked' => __('Stacked', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'currency_symbol', + [ + 'label' => __('Currency Symbol', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => '$', + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'size', + [ + 'label' => __('Size', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'medium', + 'options' => [ + 'small' => __('Small', 'cannaiq-menus'), + 'medium' => __('Medium', 'cannaiq-menus'), + 'large' => __('Large', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'price_color', + [ + 'label' => __('Price Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#1f2937', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-price-block__regular' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'sale_color', + [ + 'label' => __('Sale Price Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#dc2626', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-price-block__sale' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'original_color', + [ + 'label' => __('Original Price Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#9ca3af', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-price-block__original' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_control( + 'weight_color', + [ + 'label' => __('Weight Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#9ca3af', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-price-block__weight' => 'color: {{VALUE}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'price_typography', + 'label' => __('Price Typography', 'cannaiq-menus'), + 'selector' => '{{WRAPPER}} .cannaiq-price-block__sale, {{WRAPPER}} .cannaiq-price-block__regular', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get price values + $regular_price = 0; + $sale_price = null; + $weight = ''; + + if ($settings['source'] === 'custom') { + $regular_price = floatval($settings['custom_price']); + $sale_price = !empty($settings['custom_sale_price']) ? floatval($settings['custom_sale_price']) : null; + $weight = $settings['custom_weight']; + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $regular_price = $cannaiq_current_product['Prices'][0] + ?? $cannaiq_current_product['recPrices'][0] + ?? $cannaiq_current_product['regular_price'] + ?? 0; + + $sale_price = $cannaiq_current_product['specialPrice'] + ?? $cannaiq_current_product['sale_price'] + ?? null; + + // Check POSMetaData for prices + if (isset($cannaiq_current_product['POSMetaData']['children'][0])) { + $child = $cannaiq_current_product['POSMetaData']['children'][0]; + if (isset($child['price'])) { + $regular_price = $child['price']; + } + if (isset($child['specialPrice']) && $child['specialPrice'] > 0) { + $sale_price = $child['specialPrice']; + } + } + + $weight = $cannaiq_current_product['Options'][0] + ?? $cannaiq_current_product['rawOptions'][0] + ?? $cannaiq_current_product['weight'] + ?? ''; + } + } + + $regular_price = floatval($regular_price); + if ($regular_price <= 0) { + return; + } + + // Determine if on sale + $is_on_sale = $sale_price !== null && floatval($sale_price) > 0 && floatval($sale_price) < $regular_price; + + // Build classes + $classes = ['cannaiq-price-block']; + if ($settings['layout'] === 'stacked') { + $classes[] = 'cannaiq-price-block--stacked'; + } + if ($settings['size'] !== 'medium') { + $classes[] = 'cannaiq-price-block--' . $settings['size']; + } + + $currency = $settings['currency_symbol']; + ?> +
+ + + + + + + + + + + + +
+ start_controls_section( + 'content_section', + [ + 'label' => __('Image', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'image_source', + [ + 'label' => __('Image Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom image', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_image', + [ + 'label' => __('Choose Image', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::MEDIA, + 'default' => [ + 'url' => \Elementor\Utils::get_placeholder_image_src(), + ], + 'condition' => [ + 'image_source' => 'custom', + ], + ] + ); + + $this->add_control( + 'fallback_image', + [ + 'label' => __('Fallback Image', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::MEDIA, + 'description' => __('Shown if product has no image', 'cannaiq-menus'), + ] + ); + + $this->add_control( + 'aspect_ratio', + [ + 'label' => __('Aspect Ratio', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => '1/1', + 'options' => [ + '1/1' => __('Square (1:1)', 'cannaiq-menus'), + '4/3' => __('4:3', 'cannaiq-menus'), + '3/4' => __('3:4', 'cannaiq-menus'), + '16/9' => __('16:9', 'cannaiq-menus'), + 'auto' => __('Auto', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'hover_effect', + [ + 'label' => __('Hover Effect', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'zoom', + 'options' => [ + 'none' => __('None', 'cannaiq-menus'), + 'zoom' => __('Zoom', 'cannaiq-menus'), + ], + ] + ); + + $this->end_controls_section(); + + // Overlay Badges Section + $this->start_controls_section( + 'overlays_section', + [ + 'label' => __('Badge Overlays', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'show_discount_badge', + [ + 'label' => __('Show Discount Badge', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'discount_position', + [ + 'label' => __('Discount Position', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'top-left', + 'options' => [ + 'top-left' => __('Top Left', 'cannaiq-menus'), + 'top-right' => __('Top Right', 'cannaiq-menus'), + 'bottom-left' => __('Bottom Left', 'cannaiq-menus'), + 'bottom-right' => __('Bottom Right', 'cannaiq-menus'), + ], + 'condition' => [ + 'show_discount_badge' => 'yes', + ], + ] + ); + + $this->add_control( + 'show_strain_badge', + [ + 'label' => __('Show Strain Badge', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'strain_position', + [ + 'label' => __('Strain Position', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'bottom-left', + 'options' => [ + 'top-left' => __('Top Left', 'cannaiq-menus'), + 'top-right' => __('Top Right', 'cannaiq-menus'), + 'bottom-left' => __('Bottom Left', 'cannaiq-menus'), + 'bottom-right' => __('Bottom Right', 'cannaiq-menus'), + ], + 'condition' => [ + 'show_strain_badge' => 'yes', + ], + ] + ); + + $this->add_control( + 'show_thc_badge', + [ + 'label' => __('Show THC Badge', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + ] + ); + + $this->add_control( + 'thc_position', + [ + 'label' => __('THC Position', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'bottom-right', + 'options' => [ + 'top-left' => __('Top Left', 'cannaiq-menus'), + 'top-right' => __('Top Right', 'cannaiq-menus'), + 'bottom-left' => __('Bottom Left', 'cannaiq-menus'), + 'bottom-right' => __('Bottom Right', 'cannaiq-menus'), + ], + 'condition' => [ + 'show_thc_badge' => 'yes', + ], + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'border_radius', + [ + 'label' => __('Border Radius', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 50, + ], + ], + 'default' => [ + 'size' => 8, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-image' => 'border-radius: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_control( + 'background_color', + [ + 'label' => __('Background Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#f9fafb', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-product-image' => 'background-color: {{VALUE}};', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Box_Shadow::get_type(), + [ + 'name' => 'box_shadow', + 'selector' => '{{WRAPPER}} .cannaiq-product-image', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get image URL + $image_url = ''; + if ($settings['image_source'] === 'custom') { + $image_url = $settings['custom_image']['url'] ?? ''; + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $image_url = $cannaiq_current_product['Image'] + ?? $cannaiq_current_product['images'][0]['url'] + ?? $cannaiq_current_product['image_url'] + ?? ''; + } + } + + // Use fallback if no image + if (empty($image_url) && !empty($settings['fallback_image']['url'])) { + $image_url = $settings['fallback_image']['url']; + } + + // Get product name for alt text + $alt_text = ''; + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $alt_text = $cannaiq_current_product['Name'] ?? $cannaiq_current_product['name'] ?? ''; + } + + // Aspect ratio style + $aspect_style = ''; + if ($settings['aspect_ratio'] !== 'auto') { + $aspect_style = 'aspect-ratio: ' . $settings['aspect_ratio'] . ';'; + } + + // Hover class + $hover_class = $settings['hover_effect'] === 'zoom' ? 'cannaiq-product-image--hover-zoom' : ''; + + // Group badges by position + $badges_by_position = []; + + if ($settings['show_discount_badge'] === 'yes') { + $badges_by_position[$settings['discount_position']][] = 'discount'; + } + if ($settings['show_strain_badge'] === 'yes') { + $badges_by_position[$settings['strain_position']][] = 'strain'; + } + if ($settings['show_thc_badge'] === 'yes') { + $badges_by_position[$settings['thc_position']][] = 'thc'; + } + + ?> +
+ + <?php echo esc_attr($alt_text); ?> + + + $badges): ?> +
+
+ + render_badge($badge_type); ?> + +
+
+ +
+ $sale) { + $percent = round((($original - $sale) / $original) * 100); + echo '' . esc_html($percent) . '% OFF'; + } + break; + + case 'strain': + if (!isset($cannaiq_current_product)) return; + + $strain = strtolower($cannaiq_current_product['strainType'] + ?? $cannaiq_current_product['strain_type'] + ?? ''); + + if (!empty($strain) && in_array($strain, ['sativa', 'indica', 'hybrid'])) { + $colors = [ + 'sativa' => '#22c55e', + 'indica' => '#8b5cf6', + 'hybrid' => '#f97316', + ]; + $color = $colors[$strain]; + echo '' . esc_html(strtoupper($strain)) . ''; + } + break; + + case 'thc': + if (!isset($cannaiq_current_product)) return; + + $thc = $cannaiq_current_product['THCContent']['range'][0] + ?? $cannaiq_current_product['THC'] + ?? $cannaiq_current_product['thc_percentage'] + ?? null; + + if ($thc !== null && $thc > 0) { + echo '' . esc_html(number_format((float)$thc, 1)) . '% THC'; + } + break; + } + } +} diff --git a/wordpress-plugin/widgets/product-loop.php b/wordpress-plugin/widgets/product-loop.php index aeed1606..8085cfdd 100644 --- a/wordpress-plugin/widgets/product-loop.php +++ b/wordpress-plugin/widgets/product-loop.php @@ -17,7 +17,7 @@ class CannaIQ_Product_Loop_Widget extends \Elementor\Widget_Base { } public function get_title() { - return __('CannaIQ Product Loop', 'cannaiq-menus'); + return __('CannaiQ Product Loop', 'cannaiq-menus'); } public function get_icon() { diff --git a/wordpress-plugin/widgets/single-product.php b/wordpress-plugin/widgets/single-product.php index 4a23e8d4..bde3ccf2 100644 --- a/wordpress-plugin/widgets/single-product.php +++ b/wordpress-plugin/widgets/single-product.php @@ -14,7 +14,7 @@ class CannaIQ_Menus_Single_Product_Widget extends \Elementor\Widget_Base { } public function get_title() { - return __('CannaIQ Single Product', 'cannaiq-menus'); + return __('CannaiQ Single Product', 'cannaiq-menus'); } public function get_icon() { diff --git a/wordpress-plugin/widgets/specials-grid.php b/wordpress-plugin/widgets/specials-grid.php index 0f1d5536..cf7e9f76 100644 --- a/wordpress-plugin/widgets/specials-grid.php +++ b/wordpress-plugin/widgets/specials-grid.php @@ -14,7 +14,7 @@ class CannaIQ_Menus_Specials_Grid_Widget extends \Elementor\Widget_Base { } public function get_title() { - return __('CannaIQ Specials/Deals', 'cannaiq-menus'); + return __('CannaiQ Specials/Deals', 'cannaiq-menus'); } public function get_icon() { diff --git a/wordpress-plugin/widgets/stock-indicator.php b/wordpress-plugin/widgets/stock-indicator.php new file mode 100644 index 00000000..f3ff8753 --- /dev/null +++ b/wordpress-plugin/widgets/stock-indicator.php @@ -0,0 +1,258 @@ +start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Stock Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom value', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_in_stock', + [ + 'label' => __('In Stock', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'format', + [ + 'label' => __('Display Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'badge', + 'options' => [ + 'badge' => __('Badge', 'cannaiq-menus'), + 'text' => __('Text', 'cannaiq-menus'), + 'dot' => __('Dot + Text', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'in_stock_text', + [ + 'label' => __('In Stock Text', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => 'In Stock', + ] + ); + + $this->add_control( + 'out_of_stock_text', + [ + 'label' => __('Out of Stock Text', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::TEXT, + 'default' => 'Out of Stock', + ] + ); + + $this->add_control( + 'show_quantity', + [ + 'label' => __('Show Quantity', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + 'description' => __('Show quantity if available', 'cannaiq-menus'), + ] + ); + + $this->add_control( + 'hide_if_in_stock', + [ + 'label' => __('Hide if In Stock', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + 'description' => __('Only show when out of stock', 'cannaiq-menus'), + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'in_stock_color', + [ + 'label' => __('In Stock Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#16a34a', + ] + ); + + $this->add_control( + 'out_of_stock_color', + [ + 'label' => __('Out of Stock Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#9ca3af', + ] + ); + + $this->add_control( + 'in_stock_bg', + [ + 'label' => __('In Stock Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#dcfce7', + 'condition' => [ + 'format' => 'badge', + ], + ] + ); + + $this->add_control( + 'out_of_stock_bg', + [ + 'label' => __('Out of Stock Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#f3f4f6', + 'condition' => [ + 'format' => 'badge', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-stock-indicator', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get stock status + $in_stock = true; + $quantity = null; + + if ($settings['source'] === 'custom') { + $in_stock = $settings['custom_in_stock'] === 'yes'; + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $status = $cannaiq_current_product['Status'] ?? ''; + $in_stock = ($status === 'Active' || $status === 'In Stock' || !empty($cannaiq_current_product['in_stock'])); + + // Get quantity if available + $quantity = $cannaiq_current_product['POSMetaData']['children'][0]['quantity'] + ?? $cannaiq_current_product['quantity'] + ?? null; + } + } + + // Hide if in stock and setting enabled + if ($in_stock && $settings['hide_if_in_stock'] === 'yes') { + return; + } + + // Determine display text + $text = $in_stock ? $settings['in_stock_text'] : $settings['out_of_stock_text']; + if ($in_stock && $settings['show_quantity'] === 'yes' && $quantity !== null) { + $text .= ' (' . intval($quantity) . ')'; + } + + // Colors + $color = $in_stock ? $settings['in_stock_color'] : $settings['out_of_stock_color']; + $bg_color = $in_stock ? $settings['in_stock_bg'] : $settings['out_of_stock_bg']; + + // Build classes + $classes = [ + 'cannaiq-stock-indicator', + $in_stock ? 'cannaiq-stock-indicator--in-stock' : 'cannaiq-stock-indicator--out-of-stock', + ]; + if ($settings['format'] === 'badge') { + $classes[] = 'cannaiq-stock-indicator--badge'; + } + + // Build style + $style = sprintf('color: %s;', esc_attr($color)); + if ($settings['format'] === 'badge') { + $style .= sprintf(' background-color: %s;', esc_attr($bg_color)); + } + + ?> + + + + + + + start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Strain Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom value', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_strain', + [ + 'label' => __('Strain Type', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'hybrid', + 'options' => [ + 'sativa' => __('Sativa', 'cannaiq-menus'), + 'indica' => __('Indica', 'cannaiq-menus'), + 'hybrid' => __('Hybrid', 'cannaiq-menus'), + ], + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'format', + [ + 'label' => __('Display Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'pill', + 'options' => [ + 'pill' => __('Pill', 'cannaiq-menus'), + 'text' => __('Text Only', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'text_format', + [ + 'label' => __('Text Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'uppercase', + 'options' => [ + 'uppercase' => __('UPPERCASE', 'cannaiq-menus'), + 'capitalize' => __('Capitalize', 'cannaiq-menus'), + 'lowercase' => __('lowercase', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'show_icon', + [ + 'label' => __('Show Icon', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => '', + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'size', + [ + 'label' => __('Size', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'medium', + 'options' => [ + 'small' => __('Small', 'cannaiq-menus'), + 'medium' => __('Medium', 'cannaiq-menus'), + 'large' => __('Large', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'sativa_color', + [ + 'label' => __('Sativa Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#22c55e', + ] + ); + + $this->add_control( + 'indica_color', + [ + 'label' => __('Indica Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#8b5cf6', + ] + ); + + $this->add_control( + 'hybrid_color', + [ + 'label' => __('Hybrid Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#f97316', + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-strain-badge', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + // Get strain type + $strain = ''; + if ($settings['source'] === 'custom') { + $strain = $settings['custom_strain']; + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + $strain = strtolower($cannaiq_current_product['strainType'] ?? $cannaiq_current_product['strain_type'] ?? ''); + } + } + + if (empty($strain)) { + return; + } + + // Normalize strain type + $strain_key = strtolower($strain); + if (!in_array($strain_key, ['sativa', 'indica', 'hybrid'])) { + return; + } + + // Format text + switch ($settings['text_format']) { + case 'uppercase': + $text = strtoupper($strain); + break; + case 'lowercase': + $text = strtolower($strain); + break; + default: + $text = ucfirst($strain); + } + + // Get color based on strain type + $color = $settings[$strain_key . '_color']; + + // Build classes + $classes = [ + 'cannaiq-strain-badge', + 'cannaiq-strain-badge--' . $settings['format'], + 'cannaiq-strain-badge--' . $strain_key, + ]; + if ($settings['size'] !== 'medium') { + $classes[] = 'cannaiq-strain-badge--' . $settings['size']; + } + + // Build style + $style = ''; + if ($settings['format'] === 'pill') { + $style = sprintf('background-color: %s; color: white;', esc_attr($color)); + } else { + $style = sprintf('color: %s;', esc_attr($color)); + } + + // Icon SVG (leaf icon) + $icon = ''; + if ($settings['show_icon'] === 'yes') { + $icon = ''; + } + + printf( + '%s%s', + esc_attr(implode(' ', $classes)), + esc_attr($style), + $icon, + esc_html($text) + ); + } +} diff --git a/wordpress-plugin/widgets/thc-meter.php b/wordpress-plugin/widgets/thc-meter.php new file mode 100644 index 00000000..83df8146 --- /dev/null +++ b/wordpress-plugin/widgets/thc-meter.php @@ -0,0 +1,295 @@ +start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'type', + [ + 'label' => __('Type', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'thc', + 'options' => [ + 'thc' => __('THC', 'cannaiq-menus'), + 'cbd' => __('CBD', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'source', + [ + 'label' => __('Value Source', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'auto', + 'options' => [ + 'auto' => __('Auto (from product)', 'cannaiq-menus'), + 'custom' => __('Custom value', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'custom_value', + [ + 'label' => __('Percentage', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 20, + 'min' => 0, + 'max' => 100, + 'step' => 0.1, + 'condition' => [ + 'source' => 'custom', + ], + ] + ); + + $this->add_control( + 'display_format', + [ + 'label' => __('Display Format', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'meter', + 'options' => [ + 'meter' => __('Meter (progress bar)', 'cannaiq-menus'), + 'badge' => __('Badge', 'cannaiq-menus'), + 'pill' => __('Pill', 'cannaiq-menus'), + 'text' => __('Text Only', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'show_label', + [ + 'label' => __('Show Label', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('Yes', 'cannaiq-menus'), + 'label_off' => __('No', 'cannaiq-menus'), + 'return_value' => 'yes', + 'default' => 'yes', + ] + ); + + $this->add_control( + 'max_percentage', + [ + 'label' => __('Max Percentage (for meter)', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 35, + 'min' => 10, + 'max' => 100, + 'description' => __('Used to calculate bar fill percentage', 'cannaiq-menus'), + 'condition' => [ + 'display_format' => 'meter', + ], + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'thc_color', + [ + 'label' => __('THC Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#22c55e', + ] + ); + + $this->add_control( + 'cbd_color', + [ + 'label' => __('CBD Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#3b82f6', + ] + ); + + $this->add_control( + 'bar_height', + [ + 'label' => __('Bar Height', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SLIDER, + 'size_units' => ['px'], + 'range' => [ + 'px' => [ + 'min' => 4, + 'max' => 20, + ], + ], + 'default' => [ + 'size' => 6, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-potency-meter__bar' => 'height: {{SIZE}}{{UNIT}};', + ], + 'condition' => [ + 'display_format' => 'meter', + ], + ] + ); + + $this->add_control( + 'bar_background', + [ + 'label' => __('Bar Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#e5e7eb', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-potency-meter__bar' => 'background-color: {{VALUE}};', + ], + 'condition' => [ + 'display_format' => 'meter', + ], + ] + ); + + $this->add_group_control( + \Elementor\Group_Control_Typography::get_type(), + [ + 'name' => 'typography', + 'selector' => '{{WRAPPER}} .cannaiq-potency-meter, {{WRAPPER}} .cannaiq-potency-badge', + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + $type = $settings['type']; + + // Get percentage value + $percentage = 0; + if ($settings['source'] === 'custom') { + $percentage = floatval($settings['custom_value']); + } else { + global $cannaiq_current_product; + if (isset($cannaiq_current_product)) { + if ($type === 'thc') { + $percentage = $cannaiq_current_product['THCContent']['range'][0] + ?? $cannaiq_current_product['THC'] + ?? $cannaiq_current_product['thc_percentage'] + ?? 0; + } else { + $percentage = $cannaiq_current_product['CBDContent']['range'][0] + ?? $cannaiq_current_product['CBD'] + ?? $cannaiq_current_product['cbd_percentage'] + ?? 0; + } + } + } + + $percentage = floatval($percentage); + if ($percentage <= 0) { + return; + } + + $label = strtoupper($type); + $color = $type === 'thc' ? $settings['thc_color'] : $settings['cbd_color']; + $formatted_value = number_format($percentage, 1) . '%'; + + switch ($settings['display_format']) { + case 'meter': + $fill_percent = min(100, ($percentage / floatval($settings['max_percentage'])) * 100); + ?> +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + : + + + + + + + : + + + +