PluginProbe
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / trunk
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms vtrunk
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 3.6.2 All 177 releases
simple-tags / inc / class.client.tagcloud.php

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

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