feat: CannaiQ Menus WordPress Plugin v2.0.0 - Modular Component Library
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
New modular component widgets: - Discount Ribbon (ribbon/pill/text styles) - Strain Badge (Sativa/Indica/Hybrid colored pills) - THC/CBD Meter (progress bars or badges) - Effects Display (styled chips with icons) - Price Block (original + sale price) - Cart Button (styled CTA linking to menu) - Stock Indicator (in/out of stock badges) - Product Image + Badges (image with overlays) New card template: - Premium Product Card (ready-to-use template) Extended dynamic tags (30+ total): - Discount %, Strain Badge, THC/CBD Badge - Effects Chips, Terpenes, Price Display - Menu URL, Stock Status, and more New files: - assets/css/components.css - includes/effects-icons.php (SVG icons) - 10 new widget files - dynamic-tags-extended.php Branding updated to "CannaiQ" throughout. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BIN
backend/public/downloads/cannaiq-menus-2.0.0.zip
Normal file
BIN
backend/public/downloads/cannaiq-menus-2.0.0.zip
Normal file
Binary file not shown.
@@ -1 +1 @@
|
||||
cannaiq-menus-1.7.0.zip
|
||||
cannaiq-menus-2.0.0.zip
|
||||
@@ -1 +1 @@
|
||||
1.7.0
|
||||
2.0.0
|
||||
|
||||
740
wordpress-plugin/assets/css/components.css
Normal file
740
wordpress-plugin/assets/css/components.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: CannaIQ Menus
|
||||
* Plugin Name: CannaiQ Menus
|
||||
* Plugin URI: https://cannaiq.co
|
||||
* Description: Display cannabis product menus from CannaIQ with Elementor integration. Real-time menu data updated daily.
|
||||
* Version: 1.7.0
|
||||
* Author: CannaIQ
|
||||
* Description: Display cannabis product menus from CannaiQ with Elementor integration. Real-time menu data updated daily.
|
||||
* Version: 2.0.0
|
||||
* Author: CannaiQ
|
||||
* Author URI: https://cannaiq.co
|
||||
* License: GPL v2 or later
|
||||
* Text Domain: cannaiq-menus
|
||||
@@ -15,7 +15,7 @@ if (!defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
define('CANNAIQ_MENUS_VERSION', '1.7.0');
|
||||
define('CANNAIQ_MENUS_VERSION', '2.0.0');
|
||||
define('CANNAIQ_MENUS_API_URL', 'https://cannaiq.co/api/v1');
|
||||
define('CANNAIQ_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('CANNAIQ_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
@@ -50,7 +50,7 @@ class CannaIQ_Menus_Plugin {
|
||||
$elements_manager->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() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>CannaIQ Menus Settings</h1>
|
||||
<p>Version <?php echo CANNAIQ_MENUS_VERSION; ?> by <a href="https://cannaiq.co" target="_blank">CannaIQ</a></p>
|
||||
<p class="description">Display real-time cannabis menus with data updated daily from CannaIQ.</p>
|
||||
<h1>CannaiQ Menus Settings</h1>
|
||||
<p>Version <?php echo CANNAIQ_MENUS_VERSION; ?> by <a href="https://cannaiq.co" target="_blank">CannaiQ</a></p>
|
||||
<p class="description">Display real-time cannabis menus with data updated daily from CannaiQ.</p>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php settings_fields('cannaiq_menus_settings'); ?>
|
||||
@@ -162,7 +203,7 @@ class CannaIQ_Menus_Plugin {
|
||||
<input type="password" id="cannaiq_api_token" name="cannaiq_api_token"
|
||||
value="<?php echo esc_attr(get_option('cannaiq_api_token')); ?>"
|
||||
class="regular-text" />
|
||||
<p class="description">Your authentication token from the CannaIQ admin dashboard. The token includes your store configuration.</p>
|
||||
<p class="description">Your authentication token from the CannaiQ admin dashboard. The token includes your store configuration.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -300,10 +341,10 @@ class CannaIQ_Menus_Plugin {
|
||||
</table>
|
||||
|
||||
<h3>Elementor Widgets</h3>
|
||||
<p>If you have Elementor installed, you can use the CannaIQ widgets:</p>
|
||||
<p>If you have Elementor installed, you can use the CannaiQ widgets:</p>
|
||||
<ul style="list-style: disc; margin-left: 20px;">
|
||||
<li><strong>CannaIQ Product Grid</strong> - Display a grid of products with filtering options</li>
|
||||
<li><strong>CannaIQ Single Product</strong> - Display a single product card</li>
|
||||
<li><strong>CannaiQ Product Grid</strong> - Display a grid of products with filtering options</li>
|
||||
<li><strong>CannaiQ Single Product</strong> - Display a single product card</li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
192
wordpress-plugin/includes/effects-icons.php
Normal file
192
wordpress-plugin/includes/effects-icons.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* Effects Icons Library
|
||||
*
|
||||
* SVG icons for cannabis effects display.
|
||||
* Used by Effects Display widget and dynamic tags.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG icon for an effect
|
||||
*
|
||||
* @param string $effect Effect name (case-insensitive)
|
||||
* @param array $args Optional args: size, class, color
|
||||
* @return string SVG HTML or empty string if not found
|
||||
*/
|
||||
function cannaiq_get_effect_icon($effect, $args = []) {
|
||||
$defaults = [
|
||||
'size' => 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' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>',
|
||||
|
||||
'relaxed' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>',
|
||||
|
||||
'sleepy' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>',
|
||||
|
||||
'euphoric' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
|
||||
|
||||
'creative' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M12 2v4"/><path d="m6.34 6.34 2.83 2.83"/><path d="M2 12h4"/><path d="m6.34 17.66 2.83-2.83"/><path d="M12 18v4"/><path d="m17.66 17.66-2.83-2.83"/><path d="M18 12h4"/><path d="m17.66 6.34-2.83 2.83"/></svg>',
|
||||
|
||||
'energetic' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
|
||||
|
||||
'focused' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>',
|
||||
|
||||
'hungry' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>',
|
||||
|
||||
'uplifted' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="m18 15-6-6-6 6"/><path d="m18 9-6-6-6 6"/></svg>',
|
||||
|
||||
'talkative' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
||||
|
||||
'giggly' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><path d="M9 9h.01"/><path d="M15 9h.01"/></svg>',
|
||||
|
||||
'aroused' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
|
||||
|
||||
'tingly' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><path d="M8 13h2"/><path d="M8 17h2"/><path d="M14 13h2"/><path d="M14 17h2"/></svg>',
|
||||
|
||||
'calm' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>',
|
||||
|
||||
'sedated' => '<svg width="{SIZE}" height="{SIZE}" viewBox="0 0 24 24" fill="none" stroke="{COLOR}" stroke-width="2" class="{CLASS}"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/><path d="M9 10h.01"/><path d="M15 10h.01"/><path d="M10 16s.5-1 2-1 2 1 2 1"/></svg>',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'<span class="cannaiq-effect-chip %s %s" style="--effect-color: %s">%s<span class="cannaiq-effect-chip__label">%s</span></span>',
|
||||
$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(
|
||||
'<div class="cannaiq-effects-container %s">%s</div>',
|
||||
esc_attr($args['class']),
|
||||
implode('', $chips)
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
510
wordpress-plugin/widgets/card-template-premium.php
Normal file
510
wordpress-plugin/widgets/card-template-premium.php
Normal file
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Premium Card Template Widget
|
||||
*
|
||||
* Pre-built product card template showcasing all modular components.
|
||||
* Includes: discount ribbon, product image with overlays, name, brand,
|
||||
* effects, price block, and cart button.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ensure effects icons are loaded
|
||||
require_once dirname(__DIR__) . '/includes/effects-icons.php';
|
||||
|
||||
class CannaIQ_Premium_Card_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_premium_card';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Premium Product Card', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-single-product';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['product', 'card', 'premium', 'template', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
// Content Section
|
||||
$this->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 '<p>' . __('No product context available. Place this widget inside a Product Loop.', 'cannaiq-menus') . '</p>';
|
||||
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',
|
||||
];
|
||||
|
||||
?>
|
||||
<div class="cannaiq-product-card">
|
||||
<!-- Image Section -->
|
||||
<?php if ($settings['show_image'] === 'yes'): ?>
|
||||
<div class="cannaiq-product-card__image">
|
||||
<div class="cannaiq-product-image" style="aspect-ratio: 1;">
|
||||
<?php if (!empty($image_url)): ?>
|
||||
<img src="<?php echo esc_url($image_url); ?>" alt="<?php echo esc_attr($name); ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Discount Ribbon -->
|
||||
<?php if ($settings['show_discount'] === 'yes' && $discount_percent > 0): ?>
|
||||
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--top-left">
|
||||
<span class="cannaiq-discount-ribbon cannaiq-discount-ribbon--ribbon"><?php echo esc_html($discount_percent); ?>% OFF</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Bottom badges -->
|
||||
<?php if (($settings['show_strain_badge'] === 'yes' && !empty($strain_type)) || ($settings['show_thc_badge'] === 'yes' && $thc > 0)): ?>
|
||||
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--bottom-left">
|
||||
<div class="cannaiq-product-image__badges">
|
||||
<?php if ($settings['show_strain_badge'] === 'yes' && !empty($strain_type) && in_array($strain_type, ['sativa', 'indica', 'hybrid'])): ?>
|
||||
<span class="cannaiq-strain-badge cannaiq-strain-badge--pill" style="background-color: <?php echo esc_attr($strain_colors[$strain_type]); ?>; color: white;"><?php echo esc_html(strtoupper($strain_type)); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($settings['show_thc_badge'] === 'yes' && $thc > 0): ?>
|
||||
<span class="cannaiq-potency-badge cannaiq-potency-badge--pill" style="background-color: #1f2937; color: white;"><?php echo esc_html(number_format((float)$thc, 1)); ?>% THC</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Body Section -->
|
||||
<div class="cannaiq-product-card__body">
|
||||
<?php if ($settings['show_name'] === 'yes' && !empty($name)): ?>
|
||||
<h3 class="cannaiq-product-card__title"><?php echo esc_html($name); ?></h3>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($settings['show_brand'] === 'yes' && !empty($brand)): ?>
|
||||
<p class="cannaiq-product-card__brand">by <?php echo esc_html($brand); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($settings['show_effects'] === 'yes' && !empty($effects)): ?>
|
||||
<div style="margin: 12px 0;">
|
||||
<?php echo cannaiq_render_effects($effects, ['limit' => $effects_limit, 'show_icon' => true, 'size' => 'small']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="cannaiq-product-card__footer">
|
||||
<?php if ($settings['show_price'] === 'yes' && $original_price > 0): ?>
|
||||
<div class="cannaiq-price-block" style="margin-bottom: 12px;">
|
||||
<?php if ($settings['show_weight'] === 'yes' && !empty($weight)): ?>
|
||||
<span class="cannaiq-price-block__weight"><?php echo esc_html($weight); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($is_on_sale): ?>
|
||||
<span class="cannaiq-price-block__original">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
|
||||
<span class="cannaiq-price-block__sale">$<?php echo esc_html(number_format((float)$sale_price, 2)); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="cannaiq-price-block__regular">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($settings['show_cart_button'] === 'yes'): ?>
|
||||
<a href="<?php echo esc_url($menu_url); ?>" class="cannaiq-cart-button cannaiq-cart-button--solid cannaiq-cart-button--full" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html($settings['button_text']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
303
wordpress-plugin/widgets/cart-button.php
Normal file
303
wordpress-plugin/widgets/cart-button.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Cart Button Widget
|
||||
*
|
||||
* Displays a styled "Add to Cart" button that links to the menu/dispensary.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Cart_Button_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_cart_button';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Cart Button', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-cart';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['cart', 'buy', 'order', 'shop', 'button', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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 = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>';
|
||||
|
||||
?>
|
||||
<a href="<?php echo esc_url($url); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>"<?php echo $target; ?>>
|
||||
<?php if ($settings['show_icon'] === 'yes' && $settings['icon_position'] === 'before'): ?>
|
||||
<?php echo $icon; ?>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($settings['button_text']); ?>
|
||||
<?php if ($settings['show_icon'] === 'yes' && $settings['icon_position'] === 'after'): ?>
|
||||
<?php echo $icon; ?>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class CannaIQ_Menus_Category_List_Widget extends \Elementor\Widget_Base {
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('CannaIQ Category List', 'cannaiq-menus');
|
||||
return __('CannaiQ Category List', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
|
||||
216
wordpress-plugin/widgets/discount-ribbon.php
Normal file
216
wordpress-plugin/widgets/discount-ribbon.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Discount Ribbon Widget
|
||||
*
|
||||
* Displays discount percentage as a positioned badge/ribbon.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Discount_Ribbon_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_discount_ribbon';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Discount Ribbon', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-price-table';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['discount', 'sale', 'ribbon', 'badge', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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(
|
||||
'<span class="%s">%s</span>',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
esc_html($text)
|
||||
);
|
||||
}
|
||||
}
|
||||
793
wordpress-plugin/widgets/dynamic-tags-extended.php
Normal file
793
wordpress-plugin/widgets/dynamic-tags-extended.php
Normal file
@@ -0,0 +1,793 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Extended Dynamic Tags
|
||||
*
|
||||
* Additional dynamic tags for v2.0 modular component system.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Include effects icons helper
|
||||
require_once dirname(__DIR__) . '/includes/effects-icons.php';
|
||||
|
||||
/**
|
||||
* Register extended CannaIQ dynamic tags
|
||||
*/
|
||||
add_action('elementor/dynamic_tags/register', function($dynamic_tags_manager) {
|
||||
// Register new tags
|
||||
$dynamic_tags_manager->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(
|
||||
'<span class="%s">%s%% OFF</span>',
|
||||
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(
|
||||
'<span class="%s" style="%s">%s</span>',
|
||||
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(
|
||||
'<span class="%s">%s</span>',
|
||||
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(
|
||||
'<span class="%s">%s</span>',
|
||||
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 '<div class="cannaiq-terpenes cannaiq-terpenes--chips">';
|
||||
foreach ($terpenes as $terp) {
|
||||
$name = $terp['name'] ?? '';
|
||||
$percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : '';
|
||||
printf(
|
||||
'<span class="cannaiq-terpene-chip"><span class="cannaiq-terpene-chip__name">%s</span><span class="cannaiq-terpene-chip__percent">%s</span></span>',
|
||||
esc_html($name),
|
||||
esc_html($percent)
|
||||
);
|
||||
}
|
||||
echo '</div>';
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
echo '<div class="cannaiq-terpenes cannaiq-terpenes--list">';
|
||||
foreach ($terpenes as $terp) {
|
||||
$name = $terp['name'] ?? '';
|
||||
$percent = isset($terp['percent']) ? number_format((float)$terp['percent'], 2) . '%' : '';
|
||||
printf(
|
||||
'<div class="cannaiq-terpene-item"><span>%s</span><span>%s</span></div>',
|
||||
esc_html($name),
|
||||
esc_html($percent)
|
||||
);
|
||||
}
|
||||
echo '</div>';
|
||||
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 '<span class="cannaiq-price-block">';
|
||||
if ($is_on_sale) {
|
||||
if ($show_original) {
|
||||
printf(
|
||||
'<span class="cannaiq-price-block__original">$%s</span>',
|
||||
esc_html(number_format((float)$original, 2))
|
||||
);
|
||||
}
|
||||
printf(
|
||||
'<span class="cannaiq-price-block__sale">$%s</span>',
|
||||
esc_html(number_format((float)$sale, 2))
|
||||
);
|
||||
} else {
|
||||
printf(
|
||||
'<span class="cannaiq-price-block__regular">$%s</span>',
|
||||
esc_html(number_format((float)$original, 2))
|
||||
);
|
||||
}
|
||||
echo '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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('<span class="%s">', esc_attr($class));
|
||||
|
||||
if ($style === 'dot') {
|
||||
echo '<span class="cannaiq-stock-indicator__dot"></span>';
|
||||
}
|
||||
|
||||
echo esc_html($text);
|
||||
echo '</span>';
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
288
wordpress-plugin/widgets/effects-display.php
Normal file
288
wordpress-plugin/widgets/effects-display.php
Normal file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Effects Display Widget
|
||||
*
|
||||
* Displays product effects as styled chips with optional icons.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Include effects icons helper
|
||||
require_once dirname(__DIR__) . '/includes/effects-icons.php';
|
||||
|
||||
class CannaIQ_Effects_Display_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_effects_display';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Effects Display', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-bullet-list';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['effects', 'happy', 'relaxed', 'sleepy', 'chips', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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);
|
||||
?>
|
||||
<div class="cannaiq-effects-container">
|
||||
<?php foreach ($effects as $effect): ?>
|
||||
<?php
|
||||
$effect_name = ucfirst(strtolower(trim($effect)));
|
||||
$effect_key = strtolower(trim($effect));
|
||||
|
||||
// Get color if using colors
|
||||
$color = '#6B7280'; // Default gray
|
||||
$style = '';
|
||||
if ($settings['use_colors'] === 'yes') {
|
||||
$color = cannaiq_get_effect_color($effect);
|
||||
$style = sprintf('--effect-color: %s;', esc_attr($color));
|
||||
} else {
|
||||
$style = sprintf('background: %s; border-color: %s;',
|
||||
esc_attr($settings['default_background']),
|
||||
esc_attr($settings['default_background'])
|
||||
);
|
||||
}
|
||||
|
||||
// Build classes
|
||||
$classes = ['cannaiq-effect-chip'];
|
||||
if ($settings['size'] !== 'medium') {
|
||||
$classes[] = 'cannaiq-effect-chip--' . $settings['size'];
|
||||
}
|
||||
?>
|
||||
<span class="<?php echo esc_attr(implode(' ', $classes)); ?>" style="<?php echo esc_attr($style); ?>">
|
||||
<?php if ($settings['show_icons'] === 'yes'): ?>
|
||||
<?php echo cannaiq_get_effect_icon($effect, [
|
||||
'size' => $icon_size,
|
||||
'color' => $settings['use_colors'] === 'yes' ? $color : 'currentColor',
|
||||
]); ?>
|
||||
<?php endif; ?>
|
||||
<span class="cannaiq-effect-chip__label"><?php echo esc_html($effect_name); ?></span>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
309
wordpress-plugin/widgets/price-block.php
Normal file
309
wordpress-plugin/widgets/price-block.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Price Block Widget
|
||||
*
|
||||
* Displays product price with optional sale price and weight.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Price_Block_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_price_block';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Price Block', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-product-price';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['price', 'sale', 'cost', 'money', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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'];
|
||||
?>
|
||||
<div class="<?php echo esc_attr(implode(' ', $classes)); ?>">
|
||||
<?php if ($settings['show_weight'] === 'yes' && !empty($weight)): ?>
|
||||
<span class="cannaiq-price-block__weight"><?php echo esc_html($weight); ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_on_sale): ?>
|
||||
<?php if ($settings['show_original_when_sale'] === 'yes'): ?>
|
||||
<span class="cannaiq-price-block__original"><?php echo esc_html($currency . number_format($regular_price, 2)); ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="cannaiq-price-block__sale"><?php echo esc_html($currency . number_format(floatval($sale_price), 2)); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="cannaiq-price-block__regular"><?php echo esc_html($currency . number_format($regular_price, 2)); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class CannaIQ_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('CannaIQ Product Grid', 'cannaiq-menus');
|
||||
return __('CannaiQ Product Grid', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
|
||||
390
wordpress-plugin/widgets/product-image-overlay.php
Normal file
390
wordpress-plugin/widgets/product-image-overlay.php
Normal file
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Product Image Overlay Widget
|
||||
*
|
||||
* Displays product image with positioned badge overlays.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Product_Image_Overlay_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_product_image_overlay';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Product Image + Badges', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-image';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['image', 'product', 'photo', 'overlay', 'badges', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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';
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="cannaiq-product-image <?php echo esc_attr($hover_class); ?>" style="<?php echo esc_attr($aspect_style); ?>">
|
||||
<?php if (!empty($image_url)): ?>
|
||||
<img src="<?php echo esc_url($image_url); ?>" alt="<?php echo esc_attr($alt_text); ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($badges_by_position as $position => $badges): ?>
|
||||
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--<?php echo esc_attr($position); ?>">
|
||||
<div class="cannaiq-product-image__badges">
|
||||
<?php foreach ($badges as $badge_type): ?>
|
||||
<?php $this->render_badge($badge_type); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function render_badge($type) {
|
||||
global $cannaiq_current_product;
|
||||
|
||||
switch ($type) {
|
||||
case 'discount':
|
||||
if (!isset($cannaiq_current_product)) return;
|
||||
|
||||
$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) {
|
||||
$percent = round((($original - $sale) / $original) * 100);
|
||||
echo '<span class="cannaiq-discount-ribbon cannaiq-discount-ribbon--pill">' . esc_html($percent) . '% OFF</span>';
|
||||
}
|
||||
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 '<span class="cannaiq-strain-badge cannaiq-strain-badge--pill" style="background-color: ' . esc_attr($color) . '; color: white;">' . esc_html(strtoupper($strain)) . '</span>';
|
||||
}
|
||||
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 '<span class="cannaiq-potency-badge cannaiq-potency-badge--pill" style="background-color: #1f2937; color: white;">' . esc_html(number_format((float)$thc, 1)) . '% THC</span>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
258
wordpress-plugin/widgets/stock-indicator.php
Normal file
258
wordpress-plugin/widgets/stock-indicator.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Stock Indicator Widget
|
||||
*
|
||||
* Displays product stock status (In Stock / Out of Stock).
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Stock_Indicator_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_stock_indicator';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Stock Indicator', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-check-circle';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['stock', 'inventory', 'available', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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));
|
||||
}
|
||||
|
||||
?>
|
||||
<span class="<?php echo esc_attr(implode(' ', $classes)); ?>" style="<?php echo esc_attr($style); ?>">
|
||||
<?php if ($settings['format'] === 'dot'): ?>
|
||||
<span class="cannaiq-stock-indicator__dot"></span>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($text); ?>
|
||||
</span>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
250
wordpress-plugin/widgets/strain-badge.php
Normal file
250
wordpress-plugin/widgets/strain-badge.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ Strain Badge Widget
|
||||
*
|
||||
* Displays strain type (Sativa/Indica/Hybrid) as a colored badge.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_Strain_Badge_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_strain_badge';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('Strain Badge', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-tags';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['strain', 'sativa', 'indica', 'hybrid', 'badge', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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 = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/></svg>';
|
||||
}
|
||||
|
||||
printf(
|
||||
'<span class="%s" style="%s">%s%s</span>',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
esc_attr($style),
|
||||
$icon,
|
||||
esc_html($text)
|
||||
);
|
||||
}
|
||||
}
|
||||
295
wordpress-plugin/widgets/thc-meter.php
Normal file
295
wordpress-plugin/widgets/thc-meter.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* CannaIQ THC/CBD Meter Widget
|
||||
*
|
||||
* Displays THC or CBD percentage as a visual meter/progress bar.
|
||||
*
|
||||
* @package CannaIQ_Menus
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class CannaIQ_THC_Meter_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'cannaiq_thc_meter';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __('THC/CBD Meter', 'cannaiq-menus');
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-skill-bar';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return ['cannaiq'];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return ['thc', 'cbd', 'potency', 'meter', 'percentage', 'cannaiq'];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
|
||||
$this->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);
|
||||
?>
|
||||
<div class="cannaiq-potency-meter cannaiq-potency-meter--<?php echo esc_attr($type); ?>">
|
||||
<?php if ($settings['show_label'] === 'yes'): ?>
|
||||
<div class="cannaiq-potency-meter__header">
|
||||
<span class="cannaiq-potency-meter__label"><?php echo esc_html($label); ?></span>
|
||||
<span class="cannaiq-potency-meter__value"><?php echo esc_html($formatted_value); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="cannaiq-potency-meter__bar">
|
||||
<div class="cannaiq-potency-meter__fill" style="width: <?php echo esc_attr($fill_percent); ?>%; background: linear-gradient(90deg, <?php echo esc_attr($color); ?> 0%, <?php echo esc_attr($color); ?> 100%);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'badge':
|
||||
?>
|
||||
<span class="cannaiq-potency-badge cannaiq-potency-badge--badge">
|
||||
<?php if ($settings['show_label'] === 'yes'): ?>
|
||||
<span class="cannaiq-potency-badge__label"><?php echo esc_html($label); ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="cannaiq-potency-badge__value"><?php echo esc_html($formatted_value); ?></span>
|
||||
</span>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'pill':
|
||||
?>
|
||||
<span class="cannaiq-potency-badge cannaiq-potency-badge--pill" style="background-color: <?php echo esc_attr($color); ?>;">
|
||||
<?php if ($settings['show_label'] === 'yes'): ?>
|
||||
<?php echo esc_html($label); ?>:
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($formatted_value); ?>
|
||||
</span>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
?>
|
||||
<span class="cannaiq-potency-badge cannaiq-potency-badge--text">
|
||||
<?php if ($settings['show_label'] === 'yes'): ?>
|
||||
<?php echo esc_html($label); ?>:
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($formatted_value); ?>
|
||||
</span>
|
||||
<?php
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user