PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
king-addons / includes / helpers / Theme_Builder / Conditions.php

Conditions.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.87, at includes/helpers/Theme_Builder/Conditions.php

323 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Theme Builder conditions evaluator.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons\Theme_Builder;
9
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 /**
15 * Evaluates stored Theme Builder display conditions.
16 */
17 class Conditions
18 {
19 /**
20 * Decode and normalize saved conditions data.
21 *
22 * @param string|array<mixed> $raw Raw meta value.
23 *
24 * @return array<string,mixed>
25 */
26 public static function normalize($raw): array
27 {
28 $data = [];
29
30 if (is_array($raw)) {
31 $data = $raw;
32 } elseif (is_string($raw) && $raw !== '') {
33 $decoded = json_decode($raw, true);
34 if (is_array($decoded)) {
35 $data = $decoded;
36 } else {
37 $unserialized = maybe_unserialize($raw);
38 $data = is_array($unserialized) ? $unserialized : [];
39 }
40 }
41
42 if (isset($data['groups']) && is_array($data['groups'])) {
43 return $data;
44 }
45
46 return ['groups' => []];
47 }
48
49 /**
50 * Default condition groups for a Theme Builder sub-location.
51 *
52 * Legacy templates stored "include all" without groups. New templates
53 * created from the Theme Builder modal get an explicit post_type rule.
54 * Filling the same default here keeps both at equal specificity so a
55 * later stub cannot hide a fully built Single template.
56 *
57 * @param string $sub_location Sub-location value.
58 *
59 * @return array<string,mixed>
60 */
61 public static function defaults_for_sub_location(string $sub_location): array
62 {
63 $groups = [];
64
65 switch ($sub_location) {
66 case 'single_post':
67 $groups[] = [
68 'relation' => 'AND',
69 'rules' => [
70 [
71 'type' => 'include',
72 'target' => 'post_type',
73 'operator' => 'in',
74 'value' => ['post'],
75 ],
76 ],
77 ];
78 break;
79 case 'single_page':
80 $groups[] = [
81 'relation' => 'AND',
82 'rules' => [
83 [
84 'type' => 'include',
85 'target' => 'post_type',
86 'operator' => 'in',
87 'value' => ['page'],
88 ],
89 ],
90 ];
91 break;
92 case 'search_results':
93 $groups[] = [
94 'relation' => 'AND',
95 'rules' => [
96 [
97 'type' => 'include',
98 'target' => 'search',
99 'operator' => 'in',
100 'value' => ['search'],
101 ],
102 ],
103 ];
104 break;
105 case 'not_found':
106 $groups[] = [
107 'relation' => 'AND',
108 'rules' => [
109 [
110 'type' => 'include',
111 'target' => '404',
112 'operator' => 'in',
113 'value' => ['404'],
114 ],
115 ],
116 ];
117 break;
118 default:
119 $groups[] = [
120 'relation' => 'AND',
121 'rules' => [],
122 ];
123 break;
124 }
125
126 return ['groups' => $groups];
127 }
128
129 /**
130 * Check whether a conditions set requires Pro.
131 *
132 * @param array<string,mixed> $conditions Conditions payload.
133 *
134 * @return bool
135 */
136 public static function is_pro_only(array $conditions): bool
137 {
138 $groups = $conditions['groups'] ?? [];
139 if (empty($groups)) {
140 return false;
141 }
142
143 foreach ($groups as $group) {
144 $rules = $group['rules'] ?? [];
145 foreach ($rules as $rule) {
146 $target = $rule['target'] ?? '';
147 $values = is_array($rule['value'] ?? null) ? ($rule['value'] ?? []) : [];
148
149 if (!in_array($target, self::get_free_targets(), true)) {
150 return true;
151 }
152
153 if ('post_type' === $target && array_diff($values, ['post', 'page'])) {
154 return true;
155 }
156
157 if (in_array($target, ['post_id', 'term', 'author', 'front_page', 'blog_page'], true)) {
158 return true;
159 }
160 }
161 }
162
163 return false;
164 }
165
166 /**
167 * Evaluate conditions against current context.
168 *
169 * @param array<string,mixed> $conditions Conditions payload.
170 * @param array<string,mixed> $context Request context.
171 *
172 * @return bool
173 */
174 public static function evaluate(array $conditions, array $context): bool
175 {
176 $groups = $conditions['groups'] ?? [];
177 if (empty($groups)) {
178 return true;
179 }
180
181 foreach ($groups as $group) {
182 if (self::evaluate_group($group, $context)) {
183 return true;
184 }
185 }
186
187 return false;
188 }
189
190 /**
191 * Get free-condition targets.
192 *
193 * @return array<int,string>
194 */
195 private static function get_free_targets(): array
196 {
197 return ['post_type', 'search', '404'];
198 }
199
200 /**
201 * Evaluate a single group with relation.
202 *
203 * @param array<string,mixed> $group Conditions group.
204 * @param array<string,mixed> $context Request context.
205 *
206 * @return bool
207 */
208 private static function evaluate_group(array $group, array $context): bool
209 {
210 $relation = strtoupper($group['relation'] ?? 'AND');
211 $rules = $group['rules'] ?? [];
212
213 if (empty($rules)) {
214 return true;
215 }
216
217 $results = [];
218
219 foreach ($rules as $rule) {
220 $match = self::match_rule($rule, $context);
221 $type = $rule['type'] ?? 'include';
222
223 if ('exclude' === $type) {
224 $results[] = !$match;
225 } else {
226 $results[] = $match;
227 }
228 }
229
230 if ('OR' === $relation) {
231 return in_array(true, $results, true);
232 }
233
234 return !in_array(false, $results, true);
235 }
236
237 /**
238 * Evaluate a single rule.
239 *
240 * @param array<string,mixed> $rule Rule data.
241 * @param array<string,mixed> $context Request context.
242 *
243 * @return bool
244 */
245 private static function match_rule(array $rule, array $context): bool
246 {
247 $target = $rule['target'] ?? '';
248 $values = $rule['value'] ?? [];
249 $operator = $rule['operator'] ?? 'in';
250
251 if (!is_array($values)) {
252 $values = [$values];
253 }
254
255 switch ($target) {
256 case 'post_type':
257 return self::match_in((array) ($context['post_type'] ?? null), $values, $operator);
258 case 'post_id':
259 return self::match_in([(int) ($context['post_id'] ?? 0)], array_map('intval', $values), $operator);
260 case 'term':
261 $term_id = (int) ($context['term_id'] ?? 0);
262 $taxonomy = $context['taxonomy'] ?? '';
263 $filtered = array_filter($values, static function ($item) use ($taxonomy) {
264 if (is_array($item) && isset($item['taxonomy'], $item['id'])) {
265 return $taxonomy === ($item['taxonomy'] ?? '');
266 }
267 return true;
268 });
269 $term_values = array_map(static function ($item) {
270 if (is_array($item) && isset($item['id'])) {
271 return (int) $item['id'];
272 }
273 return (int) $item;
274 }, $filtered);
275 return self::match_in([$term_id], $term_values, $operator);
276 case 'author':
277 return self::match_in([(int) ($context['author_id'] ?? 0)], array_map('intval', $values), $operator);
278 case 'front_page':
279 return !empty($context['is_front_page']);
280 case 'blog_page':
281 return !empty($context['is_blog_page']);
282 case 'search':
283 return 'search' === ($context['type'] ?? '');
284 case '404':
285 return 'not_found' === ($context['type'] ?? '');
286 default:
287 return false;
288 }
289 }
290
291 /**
292 * Match values with operator.
293 *
294 * @param array<int|string> $current Current context values.
295 * @param array<int|string> $allowed Allowed values.
296 * @param string $operator Operator string.
297 *
298 * @return bool
299 */
300 private static function match_in(array $current, array $allowed, string $operator): bool
301 {
302 $operator = strtolower($operator ?: 'in');
303 $current = array_filter($current, static fn($value) => null !== $value && '' !== $value);
304 $allowed = array_filter($allowed, static fn($value) => null !== $value && '' !== $value);
305
306 if (empty($allowed)) {
307 return false;
308 }
309
310 $intersection = array_intersect($current, $allowed);
311
312 if ('not_in' === $operator) {
313 return empty($intersection);
314 }
315
316 return !empty($intersection);
317 }
318 }
319
320
321
322
323