PluginProbe
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / 3.50.0
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms v3.50.0
3.54.0 3.53.0 3.52.0 3.51.0 3.50.0 3.45.0 3.38.0 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.40.0 3.40.1 3.41.0 3.42.0 3.43.0 3.44.0 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 All 178 releases
simple-tags / inc / class.client.tagcloud.php

class.client.tagcloud.php in Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms 3.50.0, at inc/class.client.tagcloud.php

1,304 lines 46.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class SimpleTags_Client_TagCloud
4 {
5 /**
6 * Init Embedded tag cloud
7 *
8 * SimpleTags_Client_TagCloud constructor.
9 */
10 public function __construct()
11 {
12
13 }
14
15 /**
16 * Sort an array without accent for naturel order :)
17 *
18 * @param string $a
19 * @param string $b
20 *
21 * @return boolean
22 */
23 public static function uksort_by_name($a = '', $b = '')
24 {
25 return strnatcasecmp(remove_accents($a), remove_accents($b));
26 }
27
28 /**
29 * Generate extended tag cloud
30 *
31 * @param string $args
32 *
33 * @return string|array
34 */
35 public static function extendedTagCloud($args = '', $copyright = true)
36 {
37 $defaults = array(
38 // Simple Tag global options defaults
39 'selectionby' => 'count',
40 'selection' => 'desc',
41 'orderby' => 'random',
42 'order' => 'asc',
43 'format' => 'border',
44 'xformat' => __('<a href="%tag_link%" id="tag-link-%tag_id%" class="st-tags t%tag_scale%" title="%tag_count% topics" %tag_rel% style="%tag_size% %tag_color%">%tag_name%</a>', 'simple-tags'),
45 'number' => 20,
46 'notagstext' => __('No tags.', 'simple-tags'),
47 'title' => __('<h4>Tag Cloud</h4>', 'simple-tags'),
48 'maxcolor' => '#000000',
49 'mincolor' => '#353535',
50 'largest' => 12,
51 'smallest' => 12,
52 'unit' => 'pt',
53 'taxonomy' => 'post_tag', // Note: saved as an option but no UI to set it
54 'parent_term' => '',
55 'display_mode' => 'parents_and_sub',
56 // Simple Tag other defaults
57 'size' => 'true',
58 'color' => 'true',
59 'exclude' => '',
60 'exclude_terms' => '',
61 'include' => '',
62 'term_selection_mode' => 'automatic',
63 'include_terms' => '',
64 'limit_days' => 0,
65 'min_usage' => 0,
66 'category' => 0,
67 'ID' => 0,
68 'hide_title' => 0,
69 'hide_output' => 0,
70 'post_type' => '',
71 'wrap_class' => '',
72 'link_class' => '',
73 'before' => '',
74 'after' => '',
75 'hide_terms' => 0,
76 );
77
78 // Get options
79 $options = SimpleTags_Plugin::get_option();
80 $enable_hidden_terms = SimpleTags_Plugin::get_option_value('enable_hidden_terms');
81
82 // Get values in DB
83 $defaults['selectionby'] = $options['cloud_selectionby'];
84 $defaults['selection'] = $options['cloud_selection'];
85 $defaults['orderby'] = $options['cloud_orderby'];
86 $defaults['order'] = $options['cloud_order'];
87 $defaults['format'] = $options['cloud_format'];
88 $defaults['xformat'] = $options['cloud_xformat'];
89 $defaults['number'] = $options['cloud_limit_qty'];
90 $defaults['notagstext'] = $options['cloud_notagstext'];
91 $defaults['title'] = $options['cloud_title'];
92 $defaults['maxcolor'] = $options['cloud_max_color'];
93 $defaults['mincolor'] = $options['cloud_min_color'];
94 $defaults['largest'] = $options['cloud_max_size'];
95 $defaults['smallest'] = $options['cloud_min_size'];
96 $defaults['unit'] = $options['cloud_unit'];
97 $defaults['taxonomy'] = $options['cloud_taxonomy'];
98 $defaults['parent_term'] = $options['cloud_parent_term'];
99 $defaults['display_mode'] = $options['cloud_display_mode'];
100
101 $adv_usage = $options['cloud_adv_usage'];
102 if (empty($args)) {
103 $args = $adv_usage;
104 } else {
105 $args = $adv_usage . "&" . $args;
106 }
107 $args = wp_parse_args($args, $defaults);
108
109 // Add compatibility tips with old field syntax
110 if (isset($args['cloud_sort'])) {
111 $args['cloud_order'] = $args['cloud_sort'];
112 unset($args['cloud_sort']);
113 }
114
115 // Translate selection order
116 if (isset($args['cloud_order'])) {
117 $args['orderby'] = self::compatOldOrder($args['cloud_order'], 'orderby');
118 $args['order'] = self::compatOldOrder($args['cloud_order'], 'order');
119 }
120
121 // Category names to ID codes
122 if (isset($args['category'])) {
123 $category = explode(",", $args['category']);
124 foreach ($category as $key => $name) {
125 $category[ $key ] = is_numeric($name) ? $name : get_cat_ID($name);
126 if ($category[ $key ] == 0) {
127 unset($category[ $key ]);
128 }
129 }
130 $args['category'] = implode(",", $category);
131 }
132
133 // Get correct taxonomy ?
134 $taxonomy = self::_get_current_taxonomy($args['taxonomy']);
135
136 // Get terms
137 $term_selection_mode = isset($args['term_selection_mode']) ? $args['term_selection_mode'] : 'automatic';
138
139 if ($term_selection_mode === 'custom') {
140 $terms = [];
141 if (!empty($args['include_terms'])) {
142 $include_terms_array = array_map('trim', explode(',', $args['include_terms']));
143 foreach ($include_terms_array as $term_name) {
144 if (!empty($term_name)) {
145 $term = get_term_by('name', $term_name, $taxonomy);
146 if ($term && !is_wp_error($term)) {
147 $terms[] = $term;
148 }
149 }
150 }
151 }
152 } elseif ($term_selection_mode === 'combined') {
153 $terms = self::getTags($args, $taxonomy);
154
155 if (!empty($args['include_terms'])) {
156 $include_terms_array = array_map('trim', explode(',', $args['include_terms']));
157 $existing_term_ids = wp_list_pluck($terms, 'term_id');
158
159 foreach ($include_terms_array as $term_name) {
160 if (!empty($term_name)) {
161 $term = get_term_by('name', $term_name, $taxonomy);
162 if ($term && !is_wp_error($term) && !in_array($term->term_id, $existing_term_ids)) {
163 $terms[] = $term;
164 $existing_term_ids[] = $term->term_id;
165 }
166 }
167 }
168 }
169 } else {
170 $terms = self::getTags($args, $taxonomy);
171 }
172
173 // Remove hidden terms if enabled
174 if ($enable_hidden_terms && !empty($args['hide_terms'])) {
175 $hidden_terms = get_transient('taxopress_hidden_terms_' . $args['taxonomy']);
176 if (!empty($hidden_terms) && is_array($hidden_terms)) {
177 $terms = array_filter($terms, function ($term) use ($hidden_terms) {
178 return !in_array($term->term_id, $hidden_terms);
179 });
180 }
181 }
182
183 extract($args); // Params to variables
184
185 // Process exclude_terms
186 if (! empty($exclude_terms)) {
187 // Convert the exclude terms string into a set (associative array) for faster lookup
188 $exclude_terms_set = array_flip(array_map('strtolower', array_map('trim', explode(',', $exclude_terms))));
189 } else {
190 $exclude_terms_set = array();
191 }
192
193 // Filter out excluded terms
194 if (! empty($exclude_terms_set)) {
195 // Use array_filter to remove terms that exist in the exclude set
196 $terms = array_filter($terms, function ($term) use ($exclude_terms_set) {
197 return ! isset($exclude_terms_set[ strtolower($term->name) ]);
198 });
199 }
200
201 // If empty use default xformat !
202 if (empty($xformat)) {
203 $xformat = $defaults['xformat'];
204 }
205
206 $xformat = taxopress_sanitize_text_field($xformat);
207
208 //remove title if in settings
209 if ((int)$hide_title > 0) {
210 $title = '';
211 }
212
213 if (empty($terms)) {
214 // Check if custom mode is selected with empty include_terms
215 if ($term_selection_mode === 'custom' && empty($args['include_terms'])) {
216 $message_text = __('You have selected Custom mode but the "Custom terms to display" field is empty. Please add terms to display or switch to Automatic mode.', 'simple-tags');
217 // Apply the same styling as terms would have
218 $font_size = !empty($smallest) ? $smallest : 12;
219 $font_color = !empty($mincolor) ? $mincolor : '#353535';
220 $font_unit = !empty($unit) ? $unit : 'pt';
221 $custom_mode_message = '<span style="font-size: ' . esc_attr($font_size) . esc_attr($font_unit) . '; color: ' . esc_attr($font_color) . ';">' . esc_html($message_text) . '</span>';
222 if ((int)$hide_output === 0) {
223 return SimpleTags_Client::output_content('st-tag-cloud', $format, $title, $custom_mode_message, $copyright, '', $wrap_class, $link_class);
224 } else {
225 return '';
226 }
227 }
228
229 if ((int)$hide_output === 0) {
230 return SimpleTags_Client::output_content('st-tag-cloud', $format, $title, $notagstext, $copyright, '', $wrap_class, $link_class);
231 } else {
232 return '';
233 }
234 }
235
236 $counts = $terms_data = array();
237 foreach ((array) $terms as $term) {
238 $counts[ $term->name ] = $term->count;
239 $terms_data[ $term->name ] = $term;
240 }
241
242 // Remove temp data from memory
243 $terms = array();
244 unset($terms);
245
246 // Use full RBG code
247 if (strlen($maxcolor) == 4) {
248 $maxcolor = $maxcolor . substr($maxcolor, 1, strlen($maxcolor));
249 }
250 if (strlen($mincolor) == 4) {
251 $mincolor = $mincolor . substr($mincolor, 1, strlen($mincolor));
252 }
253
254 // Check as smallest inferior or egal to largest
255 if ($smallest > $largest) {
256 $smallest = $largest;
257 }
258
259 // Scaling - Hard value for the moment
260 $scale_min = 0;
261 $scale_max = 10;
262
263 $minval = min($counts);
264 $maxval = max($counts);
265
266 $minout = max($scale_min, 0);
267 $maxout = max($scale_max, $minout);
268
269 $scale = ($maxval > $minval) ? (($maxout - $minout) / ($maxval - $minval)) : 0;
270
271 // HTML Rel (tag/no-follow)
272 $rel = SimpleTags_Client::get_rel_attribut();
273
274 // Remove color marquer if color = false
275 if ($color == 'false') {
276 $xformat = str_replace('%tag_color%', '', $xformat);
277 }
278
279 // Remove size marquer if size = false
280 if ($size == 'false') {
281 $xformat = str_replace('%tag_size%', '', $xformat);
282 }
283
284
285 // Order terms before output
286 // count, name, rand | asc, desc
287 $orderby = strtolower($orderby);
288 $order = strtolower($order);
289
290 if (in_array($format, ['parent/child']) && $args['display_mode'] === 'parents_and_sub') {
291 $terms = self::getTags($args, $taxonomy);
292
293 // Check if terms is empty or invalid
294 if (empty($terms)) {
295 return SimpleTags_Client::output_content('st-tag-cloud', $format, $title, $notagstext, $copyright, '', $wrap_class, $link_class);
296 }
297
298 // First, group terms by parent term
299 $grouped_terms = [];
300 foreach ($terms as $term) {
301 // Only group terms with a parent (excluding standalone terms)
302 if ($term->parent != 0) {
303 $parent_id = $term->parent;
304 if (!isset($grouped_terms[$parent_id])) {
305 $grouped_terms[$parent_id] = [];
306 }
307 $grouped_terms[$parent_id][] = $term;
308 }
309 }
310
311 // Then, sort each group by name
312 foreach ($grouped_terms as &$parent_group) {
313 usort($parent_group, function ($a, $b) {
314 return strcmp($a->name, $b->name);
315 });
316 }
317
318 // Merge all grouped terms into a single array, ensuring correct parent-child order
319 $terms = [];
320 foreach ($grouped_terms as $parent_group) {
321 $terms = array_merge($terms, $parent_group);
322 }
323 }
324
325 // Custom drag-and-drop order: always check first!
326 if ($orderby === 'taxopress_term_order') {
327 $custom_order = get_option('taxopress_term_order_' . $taxonomy, []);
328 if (!empty($custom_order)) {
329 $terms_by_id = [];
330 foreach ($terms_data as $term_name => $term_obj) {
331 if (is_object($term_obj) && isset($term_obj->term_id)) {
332 $terms_by_id[$term_obj->term_id] = $term_obj;
333 }
334 }
335 $ordered_terms = [];
336 foreach ($custom_order as $term_id) {
337 if (isset($terms_by_id[$term_id])) {
338 $ordered_terms[$terms_by_id[$term_id]->name] = $terms_by_id[$term_id]->count;
339 unset($terms_by_id[$term_id]);
340 }
341 }
342 // Add any terms not in custom order at the end
343 foreach ($terms_by_id as $term_obj) {
344 $ordered_terms[$term_obj->name] = $term_obj->count;
345 }
346 if ($order === 'desc') {
347 $ordered_terms = array_reverse($ordered_terms, true);
348 }
349 $counts = $ordered_terms;
350 }
351 } else {
352 // Default sorting logic
353 if ($orderby == 'count') {
354 asort($counts);
355 } elseif ($orderby == 'name') {
356 uksort($counts, [__CLASS__, 'uksort_by_name']);
357 } else { // rand
358 SimpleTags_Client::random_array($counts);
359 }
360 $order = strtolower($order);
361 if ($order == 'desc' && $orderby != 'random') {
362 $counts = array_reverse($counts, true);
363 }
364 }
365
366 $output = array();
367
368 //update xformat with class link class
369 if (!empty(trim($link_class))) {
370 $link_class = taxopress_format_class($link_class);
371 $xformat = taxopress_add_class_to_format($xformat, $link_class);
372 }
373
374 foreach ((array) $counts as $term_name => $count) {
375 if (! is_object($terms_data[ $term_name ])) {
376 continue;
377 }
378
379 $term = $terms_data[ $term_name ];
380 $scale_result = (int) ($scale <> 0 ? (($term->count - $minval) * $scale + $minout) : ($scale_max - $scale_min) / 2);
381 //$output[] = SimpleTags_Client::format_internal_tag( $xformat, $term, $rel, $scale_result, $scale_max, $scale_min, $largest, $smallest, $unit, $maxcolor, $mincolor );
382 $formatted = SimpleTags_Client::format_internal_tag($xformat, $term, $rel, $scale_result, $scale_max, $scale_min, $largest, $smallest, $unit, $maxcolor, $mincolor);
383 if ($format === 'table') {
384 $output[] = [
385 'html' => $formatted,
386 'count' => $term->count,
387 'term_name' => $term->name,
388 ];
389 } else {
390 $output[] = $formatted;
391 }
392 }
393
394 return SimpleTags_Client::output_content('st-tag-cloud', $format, $title, $output, $copyright, '', $wrap_class, $link_class, $before, $after, $taxonomy);
395 }
396
397
398 /**
399 * Generate extended tag result
400 *
401 * @param string $args
402 *
403 * @return array
404 */
405 public static function extendedTagResult($args = '', $copyright = true)
406 {
407 $defaults = array(
408 // Simple Tag global options defaults
409 'selectionby' => 'count',
410 'selection' => 'desc',
411 'orderby' => 'random',
412 'order' => 'asc',
413 'format' => 'border',
414 'xformat' => __('<a href="%tag_link%" id="tag-link-%tag_id%" class="st-tags t%tag_scale%" title="%tag_count% topics" %tag_rel% style="%tag_size% %tag_color%">%tag_name%</a>', 'simple-tags'),
415 'number' => 20,
416 'notagstext' => __('No tags.', 'simple-tags'),
417 'title' => __('<h4>Tag Cloud</h4>', 'simple-tags'),
418 'maxcolor' => '#000000',
419 'mincolor' => '#353535',
420 'largest' => 12,
421 'smallest' => 12,
422 'unit' => 'pt',
423 'taxonomy' => 'post_tag', // Note: saved as an option but no UI to set it
424 'parent_term' => '',
425 'display_mode' => 'parents_and_sub',
426 // Simple Tag other defaults
427 'size' => 'true',
428 'color' => 'true',
429 'exclude' => '',
430 'include' => '',
431 'limit_days' => 0,
432 'min_usage' => 0,
433 'category' => 0,
434 'hide_terms' => 0,
435 );
436
437 // Get options
438 $options = SimpleTags_Plugin::get_option();
439 $enable_hidden_terms = SimpleTags_Plugin::get_option_value('enable_hidden_terms');
440
441 // Get values in DB
442 $defaults['selectionby'] = $options['cloud_selectionby'];
443 $defaults['selection'] = $options['cloud_selection'];
444 $defaults['orderby'] = $options['cloud_orderby'];
445 $defaults['order'] = $options['cloud_order'];
446 $defaults['format'] = $options['cloud_format'];
447 $defaults['xformat'] = $options['cloud_xformat'];
448 $defaults['number'] = $options['cloud_limit_qty'];
449 $defaults['notagstext'] = $options['cloud_notagstext'];
450 $defaults['title'] = $options['cloud_title'];
451 $defaults['maxcolor'] = $options['cloud_max_color'];
452 $defaults['mincolor'] = $options['cloud_min_color'];
453 $defaults['largest'] = $options['cloud_max_size'];
454 $defaults['smallest'] = $options['cloud_min_size'];
455 $defaults['unit'] = $options['cloud_unit'];
456 $defaults['taxonomy'] = $options['cloud_taxonomy'];
457 $defaults['parent_term'] = $options['cloud_parent_term'];
458 $defaults['display_mode'] = $options['cloud_display_mode'];
459
460 $adv_usage = $options['cloud_adv_usage'];
461 if (empty($args)) {
462 $args = $adv_usage;
463 } else {
464 $args = $adv_usage . "&" . $args;
465 }
466 $args = wp_parse_args($args, $defaults);
467
468 // Add compatibility tips with old field syntax
469 if (isset($args['cloud_sort'])) {
470 $args['cloud_order'] = $args['cloud_sort'];
471 unset($args['cloud_sort']);
472 }
473
474 // Translate selection order
475 if (isset($args['cloud_order'])) {
476 $args['orderby'] = self::compatOldOrder($args['cloud_order'], 'orderby');
477 $args['order'] = self::compatOldOrder($args['cloud_order'], 'order');
478 }
479
480 // Category names to ID codes
481 if (isset($args['category'])) {
482 $category = explode(",", $args['category']);
483 foreach ($category as $key => $name) {
484 $category[ $key ] = is_numeric($name) ? $name : get_cat_ID($name);
485 if ($category[ $key ] == 0) {
486 unset($category[ $key ]);
487 }
488 }
489 $args['category'] = implode(",", $category);
490 }
491
492 // Get correct taxonomy ?
493 $taxonomy = self::_get_current_taxonomy($args['taxonomy']);
494
495 // Get terms
496 $terms = self::getTags($args, $taxonomy);
497
498 // Remove hidden terms if enabled
499 if ($enable_hidden_terms && !empty($args['hide_terms'])) {
500 $hidden_terms = get_transient('taxopress_hidden_terms_' . $args['taxonomy']);
501 if (!empty($hidden_terms) && is_array($hidden_terms)) {
502 $terms = array_filter($terms, function ($term) use ($hidden_terms) {
503 return !in_array($term->term_id, $hidden_terms);
504 });
505 }
506 }
507 extract($args); // Params to variables
508
509 // If empty use default xformat !
510 if (empty($xformat)) {
511 $xformat = $defaults['xformat'];
512 }
513
514 if (empty($terms)) {
515 return [];
516 }
517
518 $counts = $terms_data = array();
519 foreach ((array) $terms as $term) {
520 $counts[ $term->name ] = $term->count;
521 $terms_data[ $term->name ] = $term;
522 }
523
524 // Remove temp data from memory
525 $terms = array();
526 unset($terms);
527
528 // Use full RBG code
529 if (strlen($maxcolor) == 4) {
530 $maxcolor = $maxcolor . substr($maxcolor, 1, strlen($maxcolor));
531 }
532 if (strlen($mincolor) == 4) {
533 $mincolor = $mincolor . substr($mincolor, 1, strlen($mincolor));
534 }
535
536 // Check as smallest inferior or egal to largest
537 if ($smallest > $largest) {
538 $smallest = $largest;
539 }
540
541 // Scaling - Hard value for the moment
542 $scale_min = 0;
543 $scale_max = 10;
544
545 $minval = min($counts);
546 $maxval = max($counts);
547
548 $minout = max($scale_min, 0);
549 $maxout = max($scale_max, $minout);
550
551 $scale = ($maxval > $minval) ? (($maxout - $minout) / ($maxval - $minval)) : 0;
552
553 // HTML Rel (tag/no-follow)
554 $rel = SimpleTags_Client::get_rel_attribut();
555
556 // Remove color marquer if color = false
557 if ($color == 'false') {
558 $xformat = str_replace('%tag_color%', '', $xformat);
559 }
560
561 // Remove size marquer if size = false
562 if ($size == 'false') {
563 $xformat = str_replace('%tag_size%', '', $xformat);
564 }
565
566 // Order terms before output
567 // count, name, rand | asc, desc
568
569 $orderby = strtolower($orderby);
570 if ($orderby == 'count') {
571 asort($counts);
572 } elseif ($orderby == 'name') {
573 uksort($counts, array( __CLASS__, 'uksort_by_name' ));
574 } else { // rand
575 SimpleTags_Client::random_array($counts);
576 }
577
578 $order = strtolower($order);
579 if ($order == 'desc' && $orderby != 'random') {
580 $counts = array_reverse($counts);
581 }
582
583 $output = array();
584 foreach ((array) $counts as $term_name => $count) {
585 if (! is_object($terms_data[ $term_name ])) {
586 continue;
587 }
588 $output[] = $terms_data[ $term_name ];
589 }
590
591
592 return $output;
593 }
594
595 /**
596 * Check if taxonomy exist and return it, otherwise return default post tags.
597 *
598 * @param string|array $taxonomies
599 * @param boolean $force_single
600 *
601 * @return array|string
602 * @author WebFactory Ltd
603 */
604 public static function _get_current_taxonomy($taxonomies, $force_single = false)
605 {
606 if (is_array($taxonomies)) {
607 foreach ($taxonomies as $key => $value) {
608 if (! taxonomy_exists($value)) { // Remove from array is taxonomy not exist !
609 unset($taxonomies[ $key ]);
610 } elseif (true === $force_single) {// Force single instead array ?
611 return $value;
612 }
613 }
614
615 return $taxonomies;
616 } elseif (taxonomy_exists($taxonomies)) {
617 return $taxonomies;
618 }
619
620 return 'post_tag';
621 }
622
623 /**
624 * Extended get_tags public static function that use getTerms function
625 *
626 * @param string|array $args
627 * @param string|array $taxonomy
628 *
629 * @return array
630 */
631 public static function getTags($args = '', $taxonomy = 'post_tag', $post_type = '')
632 {
633 $key = md5(maybe_serialize($args) . $taxonomy . (isset($args['parent_term']) ? $args['parent_term'] : '') . (isset($args['display_mode']) ? $args['display_mode'] : ''));
634
635 // Get cache if exist
636 if ($cache = wp_cache_get('st_get_tags', 'simple-tags')) {
637 if (isset($cache[ $key ])) {
638 return apply_filters('get_tags', $cache[ $key ], $args);
639 }
640 }
641
642 // Get tags
643 if (isset($args['taxonomy'])) {
644 $taxonomy = $args['taxonomy'];
645 }
646
647 if (isset($args['parent_term'])) {
648 $parent_term = $args['parent_term'];
649 }
650
651 if (isset($args['display_mode'])) {
652 $display_mode = $args['display_mode'];
653 }
654
655 if (isset($args['max'])) {
656 $max_terms = intval($args['max']);
657 } else {
658 $max_terms = 20;
659 }
660
661 if (isset($args['hide_terms'])) {
662 $hide_terms = $args['hide_terms'];
663 }
664
665 $term_args = [
666 'taxonomy' => $taxonomy,
667 'hide_empty' => false,
668 'number' => $max_terms,
669 ];
670
671 // exclude hidden terms from being queried for display at all
672 if ($hide_terms && SimpleTags_Plugin::get_option_value('enable_hidden_terms')) {
673 $hidden_terms = get_transient('taxopress_hidden_terms_' . $taxonomy);
674 if (!empty($hidden_terms) && is_array($hidden_terms)) {
675 $term_args['exclude'] = $hidden_terms;
676 }
677 }
678
679 $is_hierarchical = is_taxonomy_hierarchical($taxonomy);
680
681 if ($is_hierarchical && isset($args['parent_term'])) {
682 $parent_term = $args['parent_term'];
683 $display_mode = isset($args['display_mode']) ? $args['display_mode'] : '';
684
685 if ($parent_term === 'all') {
686 if ($display_mode === 'parents_only') {
687 $term_args['parent'] = 0;
688 } elseif ($display_mode === 'sub_terms_only') {
689 // Get all parent terms
690 $parent_terms = get_terms([
691 'taxonomy' => $taxonomy,
692 'parent' => 0,
693 'hide_empty' => false
694 ]);
695
696 $parent_ids = wp_list_pluck($parent_terms, 'term_id');
697
698 if (!empty($parent_ids)) {
699 $sub_terms = [];
700 foreach ($parent_ids as $parent_id) {
701 $terms = get_terms([
702 'taxonomy' => $taxonomy,
703 'parent' => $parent_id,
704 'hide_empty' => false,
705 'number' => $max_terms
706 ]);
707 $sub_terms = array_merge($sub_terms, $terms);
708 if (count($sub_terms) >= $max_terms) {
709 break;
710 }
711 }
712 $term_args['include'] = wp_list_pluck($sub_terms, 'term_id');
713 } else {
714 $term_args['parent'] = -1;
715 }
716 }
717 } else {
718 // Specific parent term selected
719 if ($display_mode === 'parents_only') {
720 $term_args['include'] = [$parent_term];
721 } elseif ($display_mode === 'sub_terms_only') {
722 $term_args['parent'] = $parent_term;
723 } else {
724 // Both parent and sub-terms
725 $parent_terms = get_terms([
726 'taxonomy' => $taxonomy,
727 'include' => [$parent_term],
728 'hide_empty' => false
729 ]);
730 $sub_terms = get_terms([
731 'taxonomy' => $taxonomy,
732 'parent' => $parent_term,
733 'hide_empty' => false,
734 'number' => $max_terms
735 ]);
736
737 $all_terms = array_merge($parent_terms, $sub_terms);
738 $term_args['include'] = wp_list_pluck(array_slice($all_terms, 0, $max_terms), 'term_id');
739 }
740 }
741 }
742
743 // Ensure `max_terms` is always applied
744 if (!empty($term_args['include'])) {
745 $term_args['include'] = array_slice($term_args['include'], 0, $max_terms);
746 }
747
748
749 if (isset($args['orderby'])) {
750 $term_args['orderby'] = $args['orderby'];
751 }
752 if (isset($args['order'])) {
753 $term_args['order'] = $args['order'];
754 }
755
756 if (!empty($args['limit_days'])) {
757 $recent_posts = get_posts([
758 'post_type' => $post_type ?: 'any',
759 'post_status' => 'publish',
760 'date_query' => [
761 [
762 'after' => $args['limit_days'] . ' days ago',
763 'inclusive' => false,
764 ],
765 ],
766 'fields' => 'ids',
767 'posts_per_page' => -1,
768 ]);
769 if (!empty($recent_posts)) {
770 $terms = get_terms([
771 'taxonomy' => $taxonomy,
772 'object_ids' => $recent_posts,
773 'hide_empty' => false,
774 'number' => $max_terms,
775 ]);
776 } else {
777 $terms = [];
778 }
779 } else {
780 $terms = get_terms($term_args);
781 }
782
783 if (isset($args['orderby']) && strtolower($args['orderby']) === 'random' && is_array($terms)) {
784 shuffle($terms);
785 if (isset($args['order']) && strtolower($args['order']) === 'desc') {
786 $terms = array_reverse($terms);
787 }
788 }
789 if (empty($terms)) {
790 return [];
791 }
792
793 if ($hide_terms && SimpleTags_Plugin::get_option_value('enable_hidden_terms')) {
794 $hidden_terms = get_transient('taxopress_hidden_terms_' . $taxonomy);
795 if (!empty($hidden_terms) && is_array($hidden_terms)) {
796 $hidden_terms = array_flip($hidden_terms);
797 $terms = array_filter($terms, function ($term) use ($hidden_terms) {
798 return !isset($hidden_terms[$term->term_id]);
799 });
800 }
801 }
802
803 // Cache the result
804 $cache = [];
805 $cache[$key] = $terms;
806 wp_cache_set('st_get_tags', $cache, 'simple-tags');
807
808 $terms = apply_filters('st_get_tags', $terms, $args);
809
810 return $terms;
811 }
812
813 /**
814 * Helper public static function for keep compatibility with old options TaxoPress widgets
815 *
816 * @param string $old_value
817 * @param string $key
818 *
819 * @return string
820 * @author WebFactory Ltd
821 */
822 public static function compatOldOrder($old_value = '', $key = '')
823 {
824 $return = array();
825
826 switch (strtolower($old_value)) { // count-asc/count-desc/name-asc/name-desc/random
827 case 'count-asc':
828 $return = array( 'orderby' => 'count', 'order' => 'ASC' );
829 break;
830 case 'rand':
831 case 'random':
832 $return = array( 'orderby' => 'random', 'order' => '' );
833 break;
834 case 'name-asc':
835 $return = array( 'orderby' => 'name', 'order' => 'ASC' );
836 break;
837 case 'name-desc':
838 $return = array( 'orderby' => 'name', 'order' => 'DESC' );
839 break;
840 case 'count-desc':
841 $return = array( 'orderby' => 'count', 'order' => 'DESC' );
842 break;
843 }
844
845 if (empty($return) || ! isset($return[ $key ])) {
846 return $old_value;
847 }
848
849 return $return[ $key ];
850 }
851
852 /**
853 * Extended get_terms public static function support
854 * - Limit category
855 * - Limit days
856 * - Selection restrict
857 * - Min usage
858 *
859 * @param string|array $taxonomies
860 * @param string $args
861 *
862 * @return array|WP_Error
863 */
864 public static function getTerms($taxonomies, $args = '')
865 {
866 global $wpdb;
867 $empty_array = array();
868 $join_relation = false;
869
870 $single_taxonomy = false;
871 if (! is_array($taxonomies)) {
872 $single_taxonomy = true;
873 $taxonomies = array( $taxonomies );
874 }
875
876 foreach ((array) $taxonomies as $taxonomy) {
877 if (! taxonomy_exists($taxonomy)) {
878 $error = new WP_Error('invalid_taxonomy', __('Invalid Taxonomy'));
879
880 return $error;
881 }
882 }
883 $in_taxonomies = "'" . implode("', '", $taxonomies) . "'";
884
885 $defaults = array(
886 'orderby' => 'name',
887 'order' => 'ASC',
888 'hide_empty' => true,
889 'exclude' => array(),
890 'exclude_tree' => array(),
891 'include' => array(),
892 'number' => '',
893 'fields' => 'all',
894 'slug' => '',
895 'parent' => '',
896 'hierarchical' => true,
897 'child_of' => 0,
898 'get' => '',
899 'name__like' => '',
900 'pad_counts' => false,
901 'offset' => '',
902 'search' => '',
903 // TaxoPress added
904 'limit_days' => 0,
905 'category' => 0,
906 'min_usage' => 0,
907 'st_name__like' => '',
908 'post_type' => false
909 );
910
911 $args = wp_parse_args($args, $defaults);
912
913 // Translate selection order
914 $args['orderby'] = self::compatOldOrder($args['selectionby'], 'orderby');
915 $args['order'] = self::compatOldOrder($args['selection'], 'order');
916
917 $args['number'] = absint($args['number']);
918 $args['offset'] = absint($args['offset']);
919 $args['limit_days'] = absint($args['limit_days']);
920 $args['min_usage'] = absint($args['min_usage']);
921
922 if (! $single_taxonomy || ! is_taxonomy_hierarchical($taxonomies[0]) ||
923 '' !== $args['parent']
924 ) {
925 $args['child_of'] = 0;
926 $args['hierarchical'] = false;
927 $args['pad_counts'] = false;
928 }
929
930 if ('all' == $args['get']) {
931 $args['child_of'] = 0;
932 $args['hide_empty'] = 0;
933 $args['hierarchical'] = false;
934 $args['pad_counts'] = false;
935 }
936 extract($args, EXTR_SKIP);
937
938 if ($child_of) {
939 $hierarchy = _get_term_hierarchy($taxonomies[0]);
940 if (! isset($hierarchy[ $child_of ])) {
941 return $empty_array;
942 }
943 }
944
945 if ($parent) {
946 $hierarchy = _get_term_hierarchy($taxonomies[0]);
947 if (! isset($hierarchy[ $parent ])) {
948 return $empty_array;
949 }
950 }
951
952 // $args can be whatever, only use the args defined in defaults to compute the key
953 $filter_key = (has_filter('list_terms_exclusions')) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
954 $key = md5(serialize(compact(array_keys($defaults))) . serialize($taxonomies) . $filter_key);
955 $last_changed = wp_cache_get('last_changed', 's-terms');
956 if (! $last_changed) {
957 $last_changed = time();
958 wp_cache_set('last_changed', $last_changed, 's-terms');
959 }
960 $cache_key = "get_terms:$key:$last_changed";
961 $cache = wp_cache_get($cache_key, 's-terms');
962 if (false !== $cache) {
963 $cache = apply_filters('get_terms', $cache, $taxonomies, $args);
964
965 return $cache;
966 }
967
968 $_orderby = strtolower($orderby);
969 if ('count' == $_orderby) {
970 $orderby = 'tt.count';
971 }
972 if ('random' == $_orderby) {
973 $orderby = 'RAND()';
974 } elseif ('name' == $_orderby) {
975 $orderby = 't.name';
976 } elseif ('slug' == $_orderby) {
977 $orderby = 't.slug';
978 } elseif ('term_group' == $_orderby) {
979 $orderby = 't.term_group';
980 } elseif (empty($_orderby) || 'id' == $_orderby) {
981 $orderby = 't.term_id';
982 }
983 $orderby = apply_filters('get_terms_orderby', $orderby, $args);
984
985 if (! empty($orderby)) {
986 $orderby = "ORDER BY $orderby";
987 } else {
988 $order = '';
989 }
990
991 $where = '';
992 $inclusions = '';
993 if (! empty($include)) {
994 $exclude = '';
995 $exclude_tree = '';
996 $interms = wp_parse_id_list($include);
997 foreach ($interms as $interm) {
998 if (empty($inclusions)) {
999 $inclusions = ' AND ( t.term_id = ' . intval($interm) . ' ';
1000 } else {
1001 $inclusions .= ' OR t.term_id = ' . intval($interm) . ' ';
1002 }
1003 }
1004 }
1005
1006 if (! empty($inclusions)) {
1007 $inclusions .= ')';
1008 }
1009 $where .= $inclusions;
1010
1011 $exclusions = '';
1012
1013 $excluded_terms = array(); // Collect all terms to exclude
1014
1015 if (! empty($exclude_tree)) {
1016 $excluded_trunks = wp_parse_id_list($exclude_tree);
1017 foreach ($excluded_trunks as $extrunk) {
1018 $excluded_children = (array) get_terms($taxonomies[0], array(
1019 'child_of' => intval($extrunk),
1020 'fields' => 'ids'
1021 ));
1022 $excluded_children[] = $extrunk;
1023 foreach ($excluded_children as $exterm) {
1024 if (empty($exclusions)) {
1025 $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' ';
1026 } else {
1027 $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' ';
1028 }
1029 }
1030
1031 // Also collect terms for batch exclusion
1032 $excluded_terms = array_merge($excluded_terms, $excluded_children);
1033 }
1034 }
1035
1036 if (! empty($exclude)) {
1037 $exterms = wp_parse_id_list($exclude);
1038 foreach ($exterms as $exterm) {
1039 if (empty($exclusions)) {
1040 $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' ';
1041 } else {
1042 $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' ';
1043 }
1044 }
1045
1046 // Also collect terms for batch exclusion
1047 $excluded_terms = array_merge($excluded_terms, $exterms);
1048 }
1049
1050 // Batch exclusion logic with 'NOT IN'
1051 $excluded_terms = array_unique($excluded_terms);
1052 if (! empty($excluded_terms)) {
1053 $excluded_terms_sql = implode(',', array_map('intval', $excluded_terms));
1054 // Batch exclusion will be combined with original exclusion logic
1055 $exclusions .= " AND t.term_id NOT IN ($excluded_terms_sql)";
1056 }
1057
1058 if (! empty($exclusions)) {
1059 $exclusions .= ')';
1060 }
1061 $exclusions = apply_filters('list_terms_exclusions', $exclusions, $args);
1062 $where .= $exclusions;
1063
1064 // ST Features : Restrict category
1065 if (!empty($category)) {
1066 if (! is_array($taxonomies)) {
1067 $taxonomies = array( $taxonomies );
1068 }
1069
1070 $incategories = wp_parse_id_list($category);
1071
1072 $taxonomies = "'" . implode("', '", $taxonomies) . "'";
1073 $incategories = "'" . implode("', '", $incategories) . "'";
1074
1075 $where .= " AND tr.object_id IN ( ";
1076 $where .= "SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN $wpdb->posts as p ON tr.object_id=p.ID WHERE tt.term_id IN ($incategories) AND p.post_status='publish'";
1077 $where .= " ) ";
1078 $join_relation = true;
1079 unset($incategories, $category);
1080 }
1081
1082 // ST Features : Limit posts date
1083 if ((int)$limit_days > 0) {
1084 if ($post_type) {
1085 $post_type = "AND post_type = '$post_type'";
1086 } else {
1087 $post_type = '';
1088 }
1089 $where .= " AND tr.object_id IN ( ";
1090 $where .= "SELECT DISTINCT ID FROM $wpdb->posts AS p WHERE p.post_date_gmt > '" . date('Y-m-d H:i:s', time() - $limit_days * 86400) . "' $post_type";
1091 $where .= " ) ";
1092 $join_relation = true;
1093 unset($limit_days);
1094 } else {
1095 if ($post_type) {
1096 $where .= " AND tr.object_id IN ( ";
1097 $where .= "SELECT DISTINCT ID FROM $wpdb->posts AS p WHERE post_type = '$post_type'";
1098 $where .= " ) ";
1099 $join_relation = true;
1100 }
1101 }
1102
1103 if (! empty($slug)) {
1104 $slug = sanitize_title($slug);
1105 $where .= " AND t.slug = '$slug'";
1106 }
1107
1108 if (! empty($name__like)) {
1109 $where .= " AND t.name LIKE '{$name__like}%'";
1110 }
1111
1112 if ('' !== $parent) {
1113 $parent = (int) $parent;
1114 $where .= " AND tt.parent = '$parent'";
1115 }
1116
1117 // ST Features : Another way to search
1118 if (strpos($st_name__like, ' ') !== false) {
1119
1120 $st_terms_formatted = array();
1121 $st_terms = preg_split('/[\s,]+/', $st_name_like);
1122 foreach ((array) $st_terms as $st_term) {
1123 if (empty($st_term)) {
1124 continue;
1125 }
1126 $st_terms_formatted[] = "t.name LIKE '%" . like_escape($st_term) . "%'";
1127 }
1128
1129 $where .= " AND ( " . explode(' OR ', $st_terms_formatted) . " ) ";
1130 unset($st_term, $st_terms_formatted, $st_terms);
1131
1132 } elseif (! empty($st_name__like)) {
1133
1134 $where .= " AND t.name LIKE '%{$st_name__like}%'";
1135
1136 }
1137
1138 if (in_array($taxonomies[0], ['post_tag', 'category'])) {
1139 //TODO - count not working for attachment and/or CPT ?
1140 // ST Features : Add min usage
1141 if ($hide_empty && ! $hierarchical) {
1142 if ($min_usage == 0) {
1143 $where .= ' AND tt.count > 0';
1144 } else {
1145 $where .= $wpdb->prepare(' AND tt.count >= %d', $min_usage);
1146 }
1147 }
1148 }
1149
1150
1151 if (! empty($search)) {
1152 $search = like_escape($search);
1153 $where .= " AND (t.name LIKE '%$search%')";
1154 }
1155
1156 $selects = array();
1157 switch ($fields) {
1158 case 'all':
1159 $selects = array( 't.*', 'tt.*' );
1160 break;
1161 case 'ids':
1162 case 'id=>parent':
1163 $selects = array( 't.term_id', 'tt.parent', 'tt.count' );
1164 break;
1165 case 'names':
1166 $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name' );
1167 break;
1168 case 'count':
1169 $orderby = '';
1170 $order = '';
1171 $selects = array( 'COUNT(*)' );
1172 }
1173 $select_this = implode(', ', apply_filters('get_terms_fields', $selects, $args));
1174
1175 // Add inner to relation table ?
1176 $join_relation = $join_relation == false ? '' : "INNER JOIN $wpdb->term_relationships AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id";
1177 // Query parts are individually escaped above, $taxonomies are individually checked if they exists
1178
1179
1180 // don't limit the query results when we have to descend the family tree
1181 if (! empty($number) && ! $hierarchical && empty($child_of) && '' == $parent) {
1182 if ($offset) {
1183 $query = $wpdb->prepare(
1184 "SELECT DISTINCT $select_this
1185 FROM $wpdb->terms AS t
1186 INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
1187 $join_relation
1188 WHERE 1 = %d AND tt.taxonomy IN ($in_taxonomies)
1189 $where
1190 $orderby $order
1191 LIMIT %d, %d",
1192 1,
1193 $offset,
1194 $number
1195 );
1196 } else {
1197 $query = $wpdb->prepare(
1198 "SELECT DISTINCT $select_this
1199 FROM $wpdb->terms AS t
1200 INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
1201 $join_relation
1202 WHERE 1 = %d AND tt.taxonomy IN ($in_taxonomies)
1203 $where
1204 $orderby $order
1205 LIMIT %d",
1206 1,
1207 $number
1208 );
1209 }
1210 } else {
1211 $query = $wpdb->prepare(
1212 "SELECT DISTINCT $select_this
1213 FROM $wpdb->terms AS t
1214 INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
1215 $join_relation
1216 WHERE 1 = %d AND tt.taxonomy IN ($in_taxonomies)
1217 $where
1218 $orderby $order",
1219 1
1220 );
1221 }
1222
1223 // GROUP BY t.term_id
1224
1225 if ('count' == $fields) {
1226 $term_count = $wpdb->get_var($query);
1227
1228 return $term_count;
1229 }
1230
1231 $terms = $wpdb->get_results($query);
1232 if ('all' == $fields) {
1233 update_term_cache($terms);
1234 }
1235
1236 if (empty($terms)) {
1237 wp_cache_add($cache_key, array(), 's-terms');
1238 $terms = apply_filters('get_terms', array(), $taxonomies, $args);
1239
1240 return $terms;
1241 }
1242
1243 if ($child_of) {
1244 $children = _get_term_hierarchy($taxonomies[0]);
1245 if (! empty($children)) {
1246 $terms = &_get_term_children($child_of, $terms, $taxonomies[0]);
1247 }
1248 }
1249
1250 // Update term counts to include children.
1251 if ($pad_counts && 'all' == $fields) {
1252 _pad_term_counts($terms, $taxonomies[0]);
1253 }
1254
1255 // Make sure we show empty categories that have children.
1256 if ($hierarchical && $hide_empty && is_array($terms)) {
1257 foreach ($terms as $k => $term) {
1258 if (! $term->count) {
1259 $children = _get_term_children($term->term_id, $terms, $taxonomies[0]);
1260 if (is_array($children)) {
1261 foreach ($children as $child) {
1262 if ($child->count) {
1263 continue 2;
1264 }
1265 }
1266 }
1267
1268 // It really is empty
1269 unset($terms[ $k ]);
1270 }
1271 }
1272 }
1273 reset($terms);
1274
1275 $_terms = array();
1276 if ('id=>parent' == $fields) {
1277 while ($term = array_shift($terms)) {
1278 $_terms[ $term->term_id ] = $term->parent;
1279 }
1280 $terms = $_terms;
1281 } elseif ('ids' == $fields) {
1282 while ($term = array_shift($terms)) {
1283 $_terms[] = $term->term_id;
1284 }
1285 $terms = $_terms;
1286 } elseif ('names' == $fields) {
1287 while ($term = array_shift($terms)) {
1288 $_terms[] = $term->name;
1289 }
1290 $terms = $_terms;
1291 }
1292
1293 if (0 < $number && intval(@count($terms)) > $number) {
1294 $terms = array_slice($terms, $offset, $number);
1295 }
1296
1297 wp_cache_add($cache_key, $terms, 's-terms');
1298
1299 $terms = apply_filters('get_terms', $terms, $taxonomies, $args);
1300
1301 return $terms;
1302 }
1303 }
1304