PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 All 51 releases
fluent-cart / api / Taxonomy.php

Taxonomy.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.7.1, at api/Taxonomy.php

391 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Api;
4
5 use FluentCart\App\CPT\FluentProducts;
6 use FluentCart\App\Models\WpModels\Term;
7 use FluentCart\Framework\Support\Arr;
8 use FluentCart\Framework\Support\Collection;
9 use FluentCart\Framework\Support\Str;
10
11 class Taxonomy
12 {
13 public static function getTaxonomies($objectType = 'fluent-products', $publicOnly = true): array
14 {
15 $args = [
16 'object_type' => [$objectType]
17 ];
18
19 if ($publicOnly) {
20 $args['public'] = true;
21 }
22
23 return get_taxonomies($args);
24 }
25
26 public static function syncTaxonomyTermsToProduct(
27 $productId, string $taxonomy, array $terms
28 )
29 {
30 if (!is_taxonomy_hierarchical($taxonomy)) {
31 $terms = Term::query()->find($terms)->pluck('name')->toArray();
32 }
33
34 return wp_set_post_terms($productId, $terms, $taxonomy);
35 }
36
37 public static function deleteTaxonomyTermFromProduct(
38 $productId, string $taxonomy, int $term
39 )
40 {
41 return wp_remove_object_terms($productId, $term, $taxonomy);
42 }
43
44 public static function deleteAllTermRelationshipsFromProduct($productId, string $taxonomy)
45 {
46 return wp_delete_object_term_relationships($productId, $taxonomy);
47 }
48
49 public static function getFormattedTerms(
50 $taxonomy,
51 $hideEmpty = false,
52 $parents = null,
53 $valueKey = 'value',
54 $labelKey = 'label',
55 $prefilled = null
56 ): array
57 {
58 $terms = get_terms([
59 'taxonomy' => $taxonomy,
60 'hide_empty' => $hideEmpty,
61 ]);
62
63 if (is_wp_error($terms)) {
64 return [];
65 }
66
67
68 // Stores all terms with an empty children array.
69 $termMap = [];
70
71 foreach ($terms as $term) {
72
73 $termMap[$term->term_id] = [
74 $valueKey => (string)$term->term_id,
75 $labelKey => $term->name,
76 'parent' => (string)$term->parent,
77 'children' => []
78 ];
79 }
80
81 // Organize terms into a nested parent-child structure
82 // Iterates through $termMap and attaches each child term to its parent’s children array.
83 $formattedTerms = [];
84
85 foreach ($termMap as &$term) {
86 // If the term has a parent and the parent exists in termMap, add it as a child
87 if ($term['parent'] != "0" && isset($termMap[$term['parent']])) {
88 $termMap[$term['parent']]['children'][] = &$term;
89 } else {
90 // Otherwise, add it to the top-level array (root categories)
91 $formattedTerms[] = &$term;
92 }
93 }
94
95 // $parents narrows the response to the requested parent terms — each
96 // requested node is returned at the top level with its subtree attached.
97 // The parameter was accepted but never read: callers passing parent ids
98 // (products/fetch-term-by-parent) got the full unconstrained tree back.
99 // null keeps the full tree, which is what every other caller passes.
100 if (!empty($parents)) {
101 $requestedTerms = [];
102 foreach ((array) $parents as $parentTermId) {
103 $parentTermId = (int) $parentTermId;
104 if (isset($termMap[$parentTermId])) {
105 $requestedTerms[] = $termMap[$parentTermId];
106 }
107 }
108 $formattedTerms = $requestedTerms;
109 }
110
111 // Mark prefilled terms as selected
112 // Adds a selected key if the term ID is found in $prefilled.
113 if (!empty($prefilled)) {
114 foreach ($formattedTerms as &$term) {
115 // Check if the current term should be selected
116 if (isset($term['value'])) {
117 $term['selected'] = (is_array($prefilled) && in_array($term['value'], $prefilled)) || $term['value'] == $prefilled;
118 }
119
120 // If the term has children, apply the same logic recursively
121 if (!empty($term['children'])) {
122 foreach ($term['children'] as &$childTerm) {
123 // Check if the child term should be selected
124 if (isset($childTerm['value'])) {
125 $childTerm['selected'] = (is_array($prefilled) && in_array($childTerm['value'], $prefilled)) || $childTerm['value'] == $prefilled;
126 }
127 }
128 }
129 }
130
131 }
132
133 return $formattedTerms;
134 }
135
136 public static function getTermIdsFromTerms(array $terms): array
137 {
138 return Collection::make($terms)->pluck('term_id')->toArray();
139 }
140
141 public static function getTermIdsBySlugs($slugs, $taxonomy = 'product-categories'): array
142 {
143 if (is_string($slugs)) {
144 $slugs = array_map('trim', explode(',', $slugs));
145 }
146
147 $termIds = [];
148 foreach ($slugs as $slug) {
149 $slug = sanitize_text_field($slug);
150 if (empty($slug)) {
151 continue;
152 }
153 $term = get_term_by('slug', sanitize_title($slug), $taxonomy);
154 if (!$term || is_wp_error($term)) {
155 $term = get_term_by('name', $slug, $taxonomy);
156 }
157 if ($term && !is_wp_error($term)) {
158 $termIds[] = (int) $term->term_id;
159 }
160 }
161
162 return $termIds;
163 }
164
165 /**
166 * Parse a shortcode taxonomy attribute: "product-tags:1,2|product-brands:67".
167 *
168 * Returns [taxonomy => term ids]. A taxonomy is accepted only when it is
169 * registered on products (the same list the AJAX pager re-parses), and a
170 * term only when it belongs to that taxonomy, because the product query
171 * matches term ids without checking their taxonomy. A segment that resolves
172 * to no term is kept as an empty list so the caller can refuse to widen it.
173 */
174 public static function parseShortcodeTaxonomies(string $raw): array
175 {
176 $taxonomies = static::getTaxonomies();
177
178 // Group the raw values first so a taxonomy repeated across segments
179 // still costs a single query.
180 $rawValues = [];
181 foreach (explode('|', $raw) as $segment) {
182 $parts = explode(':', $segment, 2);
183 $taxonomy = sanitize_key(trim($parts[0]));
184 if ($taxonomy === '') {
185 continue;
186 }
187 $rawValues[$taxonomy][] = isset($parts[1]) ? $parts[1] : '';
188 }
189
190 $parsed = [];
191 foreach ($rawValues as $taxonomy => $values) {
192 $parsed[$taxonomy] = isset($taxonomies[$taxonomy])
193 ? static::resolveTermIds(implode(',', $values), $taxonomy)
194 : [];
195 }
196
197 return $parsed;
198 }
199
200 private static function resolveTermIds(string $values, string $taxonomy): array
201 {
202 $ids = array_values(array_unique(array_map('intval', array_filter(
203 array_map('trim', explode(',', $values)),
204 'ctype_digit'
205 ))));
206
207 if (!$ids) {
208 return [];
209 }
210
211 // One query per taxonomy; `include` only returns terms of $taxonomy.
212 $termIds = get_terms([
213 'taxonomy' => $taxonomy,
214 'include' => $ids,
215 'fields' => 'ids',
216 'hide_empty' => false,
217 ]);
218
219 return is_wp_error($termIds) ? [] : array_map('intval', $termIds);
220 }
221
222 public static function addTaxonomyTerms(string $taxonomy, array $terms, array $args = [])
223 {
224 $taxonomies = static::getTaxonomies();
225 if (!Arr::has($taxonomies, $taxonomy)) {
226 return false;
227 }
228
229 $termIds = [];
230
231 foreach ($terms as $term) {
232 if (term_exists($term, $taxonomy)) {
233 $termIds[] = get_term_by('name', $term, $taxonomy)->term_id;
234 } else if (is_array($newTerm = wp_insert_term($term, $taxonomy, $args))) {
235 $termIds[] = $newTerm['term_id'];
236 }
237 }
238
239 return $termIds;
240 }
241
242 public static function getTaxonomyLabels($taxonomy, $keys = [])
243 {
244 $taxonomy_object = get_taxonomy($taxonomy);
245
246 if ($taxonomy_object) {
247 // Get all labels as an array
248 $all_labels = (array)get_taxonomy_labels($taxonomy_object);
249
250 $desired_keys = [
251 'all_items',
252 'add_new_item',
253 'edit_item',
254 'name',
255 'not_found',
256 'parent_item',
257 'search_items',
258 'update_item',
259 'view_item',
260 'singular_name',
261 'no_terms',
262 ];
263
264 $desired_keys = Arr::mergeMissingValues($desired_keys, $keys);
265
266 $collectionInstance = new Collection($all_labels);
267 $labels = $collectionInstance->only($desired_keys)->toArray();
268 } else {
269 $labels = $keys;
270 }
271
272 return $labels;
273 }
274
275
276 public static function fetchTaxonomies($data): ?array
277 {
278 $search = sanitize_text_field(Arr::get($data, 'search', ''));
279 $page = (int)sanitize_text_field(Arr::get($data, 'page', 1));
280 $per_page = (int)sanitize_text_field(Arr::get($data, 'per_page', 10));
281 $sort_by = sanitize_text_field(Arr::get($data, 'sort_by', 'name'));
282 $sort_type = sanitize_text_field(Arr::get($data, 'sort_type', 'DESC'));
283 $taxonomy = sanitize_text_field(Arr::get($data, 'taxonomy', 'product-categories'));
284
285 $offset = ($page - 1) * $per_page;
286
287 $total = (int)wp_count_terms(['taxonomy' => $taxonomy, 'hide_empty' => false]);
288
289 if (!empty($search)) {
290 $search_results = get_terms([
291 'taxonomy' => $taxonomy,
292 'hide_empty' => false,
293 'search' => $search,
294 'fields' => 'ids',
295 ]);
296
297 if (is_wp_error($search_results)) {
298 wp_send_json([
299 'message' => __('Failed to fetch search results.', 'fluent-cart'),
300 ], 500);
301 }
302
303 $total = count($search_results);
304 }
305
306 $labels = self::getTaxonomyLabels($taxonomy);
307
308 $terms = get_terms([
309 'taxonomy' => $taxonomy,
310 'hide_empty' => false,
311 'orderby' => $sort_by,
312 'order' => $sort_type,
313 'number' => $per_page,
314 'offset' => $offset,
315 'search' => $search
316 ]);
317
318 if (is_wp_error($terms)) {
319 return wp_send_json([
320 /* translators: %s - taxonomy name */
321 'message' => sprintf(__('Failed to fetch %s.', 'fluent-cart'), Str::lower($labels['name'])),
322 ], 500);
323 }
324
325 // Initialize an array to hold the terms by their IDs
326 $terms_by_id = [];
327 foreach ($terms as $term) {
328 $terms_by_id[$term->term_id] = [
329 'term_id' => $term->term_id,
330 'name' => $term->name,
331 'description' => $term->description,
332 'slug' => $term->slug,
333 'parent' => $term->parent,
334 'count' => $term->count,
335 'children' => [],
336 'url' => get_term_link($term->term_id),
337 ];
338 }
339
340 // Build the hierarchy
341 $hierarchy = [];
342 foreach ($terms_by_id as $term_id => &$term_data) {
343 // If the term has a parent and the parent exists in the array, add it to the parent's children.
344 if ($term_data['parent'] && isset($terms_by_id[$term_data['parent']])) {
345 $terms_by_id[$term_data['parent']]['children'][] = &$term_data;
346 } else {
347 // If the term has no parent, it is added as a top-level category.
348 $hierarchy[] = &$term_data;
349 }
350 }
351
352 // Calculate total pages
353 $last_page = ceil($total / $per_page);
354
355 // Calculate "from" and "to"
356 $from = ($total > 0) ? (($page - 1) * $per_page) + 1 : 0;
357 $to = min($page * $per_page, $total);
358
359 return [
360 "taxonomies" => [
361 "data" => $hierarchy,
362 "total" => $total,
363 "current_page" => $page,
364 "last_page" => $last_page,
365 "per_page" => $per_page,
366 "from" => $from,
367 "to" => $to,
368 "labels" => $labels,
369 ],
370 ];
371 }
372
373
374 public static function taxonomyWithTerms(): Collection
375 {
376 $taxonomies = Taxonomy::getTaxonomies();
377
378 return Collection::make($taxonomies)
379 ->map(function ($taxonomy) {
380 return [
381 'name' => $taxonomy,
382 'label' => Str::headline($taxonomy),
383 'parent' => 0,
384 'terms' => Taxonomy::getFormattedTerms($taxonomy, false, null, 'value', 'label'),
385 ];
386 });
387
388 }
389
390 }
391