| 1 |
<?php |
| 2 |
/** |
| 3 |
* Woo Builder context helpers. |
| 4 |
* |
| 5 |
* @package King_Addons |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace King_Addons\Woo_Builder; |
| 9 |
|
| 10 |
use WC_Product; |
| 11 |
|
| 12 |
if (!defined('ABSPATH')) { |
| 13 |
exit; |
| 14 |
} |
| 15 |
|
| 16 |
/** |
| 17 |
* Utility helpers for Woo Builder. |
| 18 |
*/ |
| 19 |
class Context |
| 20 |
{ |
| 21 |
/** |
| 22 |
* Detect current WooCommerce context. |
| 23 |
* |
| 24 |
* Cart, Checkout, and My Account pages may not pass is_woocommerce() |
| 25 |
* so we check them separately. |
| 26 |
* |
| 27 |
* @return string|null |
| 28 |
*/ |
| 29 |
public static function detect_context(): ?string |
| 30 |
{ |
| 31 |
if (!function_exists('is_woocommerce')) { |
| 32 |
return null; |
| 33 |
} |
| 34 |
|
| 35 |
// Check cart page first (may not pass is_woocommerce()) |
| 36 |
if (function_exists('is_cart') && is_cart()) { |
| 37 |
return 'cart'; |
| 38 |
} |
| 39 |
|
| 40 |
// Check checkout page (may not pass is_woocommerce()) |
| 41 |
if (function_exists('is_checkout') && is_checkout() && !is_order_received_page()) { |
| 42 |
return 'checkout'; |
| 43 |
} |
| 44 |
|
| 45 |
// Check My Account page (may not pass is_woocommerce()) |
| 46 |
if (function_exists('is_account_page') && is_account_page()) { |
| 47 |
return 'my_account'; |
| 48 |
} |
| 49 |
|
| 50 |
// For product and archive pages, is_woocommerce() should be true |
| 51 |
if (!is_woocommerce()) { |
| 52 |
return null; |
| 53 |
} |
| 54 |
|
| 55 |
if (is_product()) { |
| 56 |
return 'single_product'; |
| 57 |
} |
| 58 |
|
| 59 |
if (is_shop() || is_product_category() || is_product_tag() || is_product_taxonomy()) { |
| 60 |
return 'product_archive'; |
| 61 |
} |
| 62 |
|
| 63 |
return null; |
| 64 |
} |
| 65 |
|
| 66 |
/** |
| 67 |
* Get current product ID. |
| 68 |
* |
| 69 |
* @return int|null |
| 70 |
*/ |
| 71 |
public static function get_product_id(): ?int |
| 72 |
{ |
| 73 |
global $product, $post; |
| 74 |
|
| 75 |
if ($product instanceof WC_Product) { |
| 76 |
return (int) $product->get_id(); |
| 77 |
} |
| 78 |
|
| 79 |
if (!empty($post->ID) && 'product' === get_post_type($post->ID)) { |
| 80 |
return (int) $post->ID; |
| 81 |
} |
| 82 |
|
| 83 |
return null; |
| 84 |
} |
| 85 |
|
| 86 |
/** |
| 87 |
* Match conditions array against current page context. |
| 88 |
* |
| 89 |
* @param string $context Page context. |
| 90 |
* @param array<string,mixed> $conditions Conditions array. |
| 91 |
* |
| 92 |
* @return bool |
| 93 |
*/ |
| 94 |
public static function match_conditions(string $context, array $conditions): bool |
| 95 |
{ |
| 96 |
if (empty($conditions['rules']) || !is_array($conditions['rules'])) { |
| 97 |
return true; |
| 98 |
} |
| 99 |
|
| 100 |
$product_id = self::get_product_id(); |
| 101 |
|
| 102 |
foreach ($conditions['rules'] as $rule) { |
| 103 |
$type = $rule['type'] ?? ''; |
| 104 |
$values = is_array($rule['values'] ?? null) ? $rule['values'] : []; |
| 105 |
|
| 106 |
if (!self::match_rule($context, $type, $values, $product_id)) { |
| 107 |
return false; |
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
return true; |
| 112 |
} |
| 113 |
|
| 114 |
/** |
| 115 |
* Match a single rule. |
| 116 |
* |
| 117 |
* @param string $context Context. |
| 118 |
* @param string $type Rule type. |
| 119 |
* @param array $values Rule values. |
| 120 |
* @param int|null $product_id Product ID. |
| 121 |
* |
| 122 |
* @return bool |
| 123 |
*/ |
| 124 |
private static function match_rule(string $context, string $type, array $values, ?int $product_id): bool |
| 125 |
{ |
| 126 |
switch ($type) { |
| 127 |
case 'all_products': |
| 128 |
return 'single_product' === $context; |
| 129 |
case 'product_in': |
| 130 |
case 'products': |
| 131 |
return ('single_product' === $context && $product_id && in_array($product_id, $values, true)); |
| 132 |
case 'product_cat_in': |
| 133 |
case 'product_categories': |
| 134 |
if ('single_product' !== $context || !$product_id) { |
| 135 |
return false; |
| 136 |
} |
| 137 |
$terms = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']); |
| 138 |
return !empty(array_intersect($values, $terms)); |
| 139 |
case 'product_tag_in': |
| 140 |
case 'product_tags': |
| 141 |
if ('single_product' !== $context || !$product_id) { |
| 142 |
return false; |
| 143 |
} |
| 144 |
$terms = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'ids']); |
| 145 |
return !empty(array_intersect($values, $terms)); |
| 146 |
case 'product_type_in': |
| 147 |
case 'product_types': |
| 148 |
if ('single_product' !== $context || !$product_id) { |
| 149 |
return false; |
| 150 |
} |
| 151 |
$types = wp_get_object_terms($product_id, 'product_type', ['fields' => 'slugs']); |
| 152 |
return !empty(array_intersect($values, $types)); |
| 153 |
case 'is_shop': |
| 154 |
case 'shop': |
| 155 |
return 'product_archive' === $context && is_shop(); |
| 156 |
case 'product_cat_archive_in': |
| 157 |
case 'product_cat_archives': |
| 158 |
if ('product_archive' !== $context) { |
| 159 |
return false; |
| 160 |
} |
| 161 |
if (is_product_category()) { |
| 162 |
$term = get_queried_object(); |
| 163 |
if (!empty($term->term_id)) { |
| 164 |
return in_array((int) $term->term_id, $values, true); |
| 165 |
} |
| 166 |
} |
| 167 |
return false; |
| 168 |
case 'cart': |
| 169 |
return 'cart' === $context; |
| 170 |
case 'checkout': |
| 171 |
return 'checkout' === $context; |
| 172 |
case 'my_account': |
| 173 |
return 'my_account' === $context; |
| 174 |
case 'always': |
| 175 |
return true; |
| 176 |
default: |
| 177 |
return true; |
| 178 |
} |
| 179 |
} |
| 180 |
|
| 181 |
/** |
| 182 |
* Render an editor-only notice if widget is not in the expected Woo context. |
| 183 |
* |
| 184 |
* @param string $expected_context Expected context slug. |
| 185 |
* |
| 186 |
* @return bool True when context matches or not in edit mode, false when notice rendered. |
| 187 |
*/ |
| 188 |
public static function maybe_render_context_notice(string $expected_context): bool |
| 189 |
{ |
| 190 |
if (!class_exists('\Elementor\Plugin') || !\Elementor\Plugin::$instance->editor->is_edit_mode()) { |
| 191 |
return true; |
| 192 |
} |
| 193 |
|
| 194 |
$current = self::detect_context(); |
| 195 |
if ($current === $expected_context) { |
| 196 |
return true; |
| 197 |
} |
| 198 |
|
| 199 |
$labels = [ |
| 200 |
'single_product' => esc_html__('This widget works on Single Product pages.', 'king-addons'), |
| 201 |
'product_archive' => esc_html__('This widget works on Product Archive pages.', 'king-addons'), |
| 202 |
'cart' => esc_html__('This widget works on the Cart page.', 'king-addons'), |
| 203 |
'checkout' => esc_html__('This widget works on the Checkout page.', 'king-addons'), |
| 204 |
'my_account' => esc_html__('This widget works on My Account pages.', 'king-addons'), |
| 205 |
]; |
| 206 |
|
| 207 |
$message = $labels[$expected_context] ?? esc_html__('This widget is only available on WooCommerce pages.', 'king-addons'); |
| 208 |
echo '<div class="king-addons-woo-builder-notice">' . esc_html($message) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 209 |
return false; |
| 210 |
} |
| 211 |
} |
| 212 |
|
| 213 |
|
| 214 |
|
| 215 |
|
| 216 |
|
| 217 |
|
| 218 |
|
| 219 |
|