PluginProbe
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / 3.52.0
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms v3.52.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 / terms-table.php

terms-table.php in Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms 3.52.0, at inc/terms-table.php

1,444 lines 56.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!class_exists('WP_List_Table')) {
4 require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
5 }
6
7 class Taxopress_Terms_List extends WP_List_Table
8 {
9 /** Class constructor */
10 public function __construct()
11 {
12
13 parent::__construct([
14 'singular' => 'Term', //singular name of the listed records
15 'plural' => 'Terms', //plural name of the listed records
16 'ajax' => true //does this table support ajax?
17 ]);
18 }
19
20 /**
21 * Return the current Terms screen filters in a normalized shape.
22 *
23 * @return array
24 */
25 private function get_current_term_filters()
26 {
27 $taxonomies = array_keys(get_all_taxopress_taxonomies_request());
28
29 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for search filtering
30 $search = (!empty($_REQUEST['s'])) ? sanitize_text_field(wp_unslash($_REQUEST['s'])) : '';
31
32 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering and display
33 $selected_post_type = (!empty($_REQUEST['terms_filter_post_type'])) ? sanitize_key(wp_unslash($_REQUEST['terms_filter_post_type'])) : '';
34 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering and display
35 $selected_taxonomy = (!empty($_REQUEST['terms_filter_taxonomy'])) ? sanitize_key(wp_unslash($_REQUEST['terms_filter_taxonomy'])) : '';
36
37 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
38 if (!empty($_REQUEST['taxopress_terms_taxonomy'])) {
39 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
40 $selected_taxonomy = sanitize_key(wp_unslash($_REQUEST['taxopress_terms_taxonomy']));
41 $taxonomies = [$selected_taxonomy];
42 } elseif (!empty($selected_taxonomy)) {
43 $taxonomies = [$selected_taxonomy];
44 }
45
46 return [
47 'taxonomies' => array_values(array_filter(array_map('sanitize_key', $taxonomies))),
48 'search' => $search,
49 'selected_post_type' => $selected_post_type,
50 'selected_taxonomy' => $selected_taxonomy,
51 ];
52 }
53
54 /**
55 * Hydrate a page of term references while preserving the requested order.
56 *
57 * @param array $term_refs List of arrays containing term_id and taxonomy.
58 * @return array
59 */
60 private function get_terms_from_refs($term_refs)
61 {
62 if (empty($term_refs)) {
63 return [];
64 }
65
66 $by_taxonomy = [];
67 foreach ($term_refs as $term_ref) {
68 $taxonomy = sanitize_key($term_ref['taxonomy']);
69 $term_id = (int) $term_ref['term_id'];
70
71 if ($term_id > 0 && !empty($taxonomy)) {
72 $by_taxonomy[$taxonomy][] = $term_id;
73 }
74 }
75
76 $terms_by_key = [];
77 foreach ($by_taxonomy as $taxonomy => $term_ids) {
78 $taxonomy_terms = get_terms([
79 'taxonomy' => [$taxonomy],
80 'include' => array_values(array_unique($term_ids)),
81 'hide_empty' => false,
82 'orderby' => 'include',
83 'number' => count($term_ids),
84 'pad_counts' => false,
85 'hierarchical' => false,
86 'update_term_meta_cache' => true,
87 ]);
88
89 if (empty($taxonomy_terms) || is_wp_error($taxonomy_terms)) {
90 continue;
91 }
92
93 foreach ($taxonomy_terms as $term) {
94 $terms_by_key[$term->taxonomy . ':' . $term->term_id] = $term;
95 }
96 }
97
98 $ordered_terms = [];
99 foreach ($term_refs as $term_ref) {
100 $key = sanitize_key($term_ref['taxonomy']) . ':' . (int) $term_ref['term_id'];
101 if (isset($terms_by_key[$key])) {
102 $ordered_terms[] = $terms_by_key[$key];
103 }
104 }
105
106 return $ordered_terms;
107 }
108
109 /**
110 * Add term references to an ordered list.
111 *
112 * @param array $term_refs Ordered term references.
113 * @param string $taxonomy Taxonomy slug.
114 * @param array $term_ids Term IDs.
115 */
116 private function append_term_refs(&$term_refs, $taxonomy, $term_ids)
117 {
118 foreach ((array) $term_ids as $term_id) {
119 $term_id = (int) $term_id;
120
121 if ($term_id <= 0) {
122 continue;
123 }
124
125 $term_refs[] = [
126 'term_id' => $term_id,
127 'taxonomy' => $taxonomy,
128 ];
129 }
130 }
131
132 /**
133 * Count terms for one taxonomy using the same filters as the list table.
134 *
135 * @param string $taxonomy Taxonomy slug.
136 * @param array|string $selected_post_type Selected post type filter.
137 * @param string $search Search text.
138 * @return int
139 */
140 private function count_taxonomy_terms($taxonomy, $selected_post_type, $search)
141 {
142 $terms_attr = [
143 'taxonomy' => [$taxonomy],
144 'post_types' => $selected_post_type,
145 'hide_empty' => false,
146 'pad_counts' => false,
147 'hierarchical' => false,
148 'update_term_meta_cache' => false,
149 'fields' => 'count',
150 'search' => $search,
151 ];
152
153 $term_count = get_terms($terms_attr);
154
155 return is_wp_error($term_count) ? 0 : (int) $term_count;
156 }
157
158 /**
159 * Remove deleted term IDs from the saved manual ordering option.
160 *
161 * @param array $term_ids_by_taxonomy Deleted term IDs grouped by taxonomy.
162 */
163 private function remove_terms_from_manual_order($term_ids_by_taxonomy)
164 {
165 foreach ($term_ids_by_taxonomy as $taxonomy => $term_ids) {
166 $term_ids = array_map('intval', (array) $term_ids);
167 if (empty($term_ids)) {
168 continue;
169 }
170
171 $option_name = 'taxopress_term_order_' . sanitize_key($taxonomy);
172 $custom_order = get_option($option_name, []);
173
174 if (empty($custom_order) || !is_array($custom_order)) {
175 continue;
176 }
177
178 $updated_order = array_values(array_diff(array_map('intval', $custom_order), $term_ids));
179
180 if ($updated_order !== array_values(array_map('intval', $custom_order))) {
181 update_option($option_name, $updated_order);
182 }
183 }
184 }
185
186 /**
187 * Show a just-copied term directly above its original during the next render.
188 *
189 * @param array $terms Current visible terms.
190 * @return array
191 */
192 private function prioritize_copied_term($terms)
193 {
194 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary display context after a copy action.
195 $copied_term_id = !empty($_REQUEST['taxopress_copied_term_id']) ? (int) $_REQUEST['taxopress_copied_term_id'] : 0;
196 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary display context after a copy action.
197 $original_term_id = !empty($_REQUEST['taxopress_original_term_id']) ? (int) $_REQUEST['taxopress_original_term_id'] : 0;
198 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary display context after a copy action.
199 $taxonomy = !empty($_REQUEST['taxopress_copied_taxonomy']) ? sanitize_key(wp_unslash($_REQUEST['taxopress_copied_taxonomy'])) : '';
200
201 if ($copied_term_id <= 0 || $original_term_id <= 0 || empty($taxonomy)) {
202 return $terms;
203 }
204
205 $copied_term = get_term($copied_term_id, $taxonomy);
206 $original_term = get_term($original_term_id, $taxonomy);
207
208 if (!$copied_term || !$original_term || is_wp_error($copied_term) || is_wp_error($original_term)) {
209 return $terms;
210 }
211
212 if (isset($original_term->taxopress_depth)) {
213 $copied_term->taxopress_depth = $original_term->taxopress_depth;
214 }
215
216 $ordered_terms = [];
217 $inserted_copy = false;
218
219 foreach ($terms as $term) {
220 if ((int) $term->term_id === $copied_term_id) {
221 continue;
222 }
223
224 if ((int) $term->term_id === $original_term_id) {
225 if (isset($term->taxopress_depth)) {
226 $copied_term->taxopress_depth = $term->taxopress_depth;
227 }
228
229 $ordered_terms[] = $copied_term;
230 $inserted_copy = true;
231 }
232
233 $ordered_terms[] = $term;
234 }
235
236 if (!$inserted_copy) {
237 $ordered_terms[] = $copied_term;
238 }
239
240 return $ordered_terms;
241 }
242
243 /**
244 * Flatten a hierarchical terms array into a flat array.
245 *
246 * @param array $terms Array of term objects.
247 * @param int $max_depth Maximum depth to traverse (default 10).
248 * @return array Flattened array of term objects.
249 */
250 public function taxopress_flatten_terms_tree($terms, $max_depth = 7)
251 {
252 $flat = [];
253 $map = [];
254 $children_map = [];
255 $visited = [];
256
257 // Build a map of terms by ID and their children
258 foreach ($terms as $term) {
259 $map[$term->term_id] = $term;
260 $children_map[$term->parent][] = $term;
261 }
262
263 // Initialize stack with root-level terms (no valid parent)
264 $stack = [];
265 foreach (array_reverse($terms) as $term) {
266 if ($term->parent === 0 || !isset($map[$term->parent])) {
267 $stack[] = ['term' => $term, 'depth' => 0];
268 }
269 }
270
271 // Iterative depth-first traversal
272 while (!empty($stack)) {
273 $node = array_pop($stack);
274 $term = $node['term'];
275 $depth = $node['depth'];
276
277 if ($depth > $max_depth) {
278 continue;
279 }
280 if (isset($visited[$term->term_id])) {
281 continue; // Prevent cycles
282 }
283 $visited[$term->term_id] = true;
284
285 $flat[] = $term;
286
287 // Use pre-indexed children map
288 if (!empty($children_map[$term->term_id])) {
289 foreach (array_reverse($children_map[$term->term_id]) as $child) {
290 $stack[] = ['term' => $child, 'depth' => $depth + 1];
291 }
292 }
293 }
294
295 return $flat;
296 }
297
298 /**
299 * Arrange terms in hierarchical order with depth info for dash prefixing.
300 */
301 public function taxopress_arrange_terms_hierarchically($terms)
302 {
303 $terms_by_id = [];
304 $children = [];
305 foreach ($terms as $term) {
306 $term_id = (int) $term->term_id;
307 $parent_id = (int) $term->parent;
308
309 if ($term_id <= 0 || isset($terms_by_id[$term_id])) {
310 continue;
311 }
312
313 $terms_by_id[$term_id] = $term;
314 $children[$parent_id][] = $term_id;
315 }
316
317 $ordered = [];
318 $visited = [];
319
320 $add_terms = function ($term_ids) use (&$terms_by_id, &$children, &$ordered, &$visited) {
321 $stack = [];
322
323 foreach (array_reverse($term_ids) as $term_id) {
324 $stack[] = [
325 'term_id' => (int) $term_id,
326 'depth' => 0,
327 ];
328 }
329
330 while (!empty($stack)) {
331 $node = array_pop($stack);
332 $term_id = (int) $node['term_id'];
333
334 if (isset($visited[$term_id]) || !isset($terms_by_id[$term_id])) {
335 continue;
336 }
337
338 $visited[$term_id] = true;
339 $term = $terms_by_id[$term_id];
340 $term->taxopress_depth = (int) $node['depth'];
341 $ordered[] = $term;
342
343 if (!empty($children[$term_id])) {
344 foreach (array_reverse($children[$term_id]) as $child_id) {
345 $child_id = (int) $child_id;
346
347 if ($child_id === $term_id || isset($visited[$child_id])) {
348 continue;
349 }
350
351 $stack[] = [
352 'term_id' => $child_id,
353 'depth' => (int) $node['depth'] + 1,
354 ];
355 }
356 }
357 }
358 };
359
360 // Start with root terms
361 $root_ids = [];
362 foreach ($terms as $term) {
363 $term_id = (int) $term->term_id;
364 $parent_id = (int) $term->parent;
365
366 if ($parent_id === 0 || !isset($terms_by_id[$parent_id]) || $parent_id === $term_id) {
367 $root_ids[] = $term_id;
368 }
369 }
370
371 $add_terms($root_ids);
372
373 // Include any disconnected or cyclic branches once, without recursion.
374 foreach ($terms as $term) {
375 $term_id = (int) $term->term_id;
376 if (!isset($visited[$term_id])) {
377 $add_terms([$term_id]);
378 }
379 }
380
381 return $ordered;
382 }
383
384 public function get_all_terms($count = false)
385 {
386
387 $filters = $this->get_current_term_filters();
388 $taxonomies = $filters['taxonomies'];
389 $taxonomy_settings = taxopress_get_all_edited_taxonomy_data();
390
391 $search = $filters['search'];
392
393 $items_per_page = $this->get_items_per_page('st_terms_per_page', 20);
394 $page = $this->get_pagenum();
395 $offset = ($page - 1) * $items_per_page;
396
397 $selected_post_type = !empty($filters['selected_post_type']) ? [$filters['selected_post_type']] : '';
398 $selected_taxonomy = $filters['selected_taxonomy'];
399
400 $allowed_orderby = ['name', 'slug', 'taxonomy', 'count', 'id'];
401 $allowed_order = ['asc', 'desc'];
402
403 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for ordering
404 $requested_orderby = !empty($_REQUEST['orderby']) ? sanitize_key(wp_unslash($_REQUEST['orderby'])) : '';
405 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for ordering
406 $requested_order = !empty($_REQUEST['order']) ? strtolower(sanitize_text_field(wp_unslash($_REQUEST['order']))) : '';
407
408 if (!in_array($requested_orderby, $allowed_orderby, true)) {
409 $requested_orderby = '';
410 }
411 if (!in_array($requested_order, $allowed_order, true)) {
412 $requested_order = '';
413 }
414
415 $order_setting = isset($taxonomy_settings[$selected_taxonomy]['order']) ? strtolower($taxonomy_settings[$selected_taxonomy]['order']) : 'desc';
416 $orderby_setting = isset($taxonomy_settings[$selected_taxonomy]['orderby']) ? $taxonomy_settings[$selected_taxonomy]['orderby'] : 'ID';
417
418 $orderby_setting = strtolower($orderby_setting);
419 if ($orderby_setting === 'id' || $orderby_setting === 'term_id') {
420 $orderby_setting = 'id';
421 }
422
423 if ($requested_orderby) {
424 $orderby_setting = $requested_orderby;
425 }
426 if ($requested_order) {
427 $order_setting = $requested_order;
428 }
429
430 $show_all_terms_in_taxonomy = false;
431
432 // Check if any taxonomy uses manual ordering
433 $manual_order = false;
434 foreach ($taxonomies as $taxonomy) {
435 $order_setting = isset($taxonomy_settings[$taxonomy]['order']) ? $taxonomy_settings[$taxonomy]['order'] : 'desc';
436 $orderby_setting = isset($taxonomy_settings[$taxonomy]['orderby']) ? $taxonomy_settings[$taxonomy]['orderby'] : 'ID';
437 if ($requested_orderby) {
438 $orderby_setting = $requested_orderby;
439 }
440 if ($requested_order) {
441 $order_setting = $requested_order;
442 }
443 if ($orderby_setting === 'taxopress_term_order') {
444 $manual_order = true;
445 break;
446 }
447 }
448
449 // If any taxonomy uses manual order, use the original per-taxonomy logic
450 if ($manual_order) {
451 $term_refs = [];
452 $remaining_offset = $count ? 0 : $offset;
453 $remaining_number = $count ? PHP_INT_MAX : $items_per_page;
454 foreach ($taxonomies as $taxonomy) {
455 if ($remaining_number <= 0) {
456 break;
457 }
458
459 $custom_order = get_option('taxopress_term_order_' . $taxonomy, []);
460 $custom_order = array_values(array_filter(array_map('intval', (array) $custom_order)));
461 $custom_order = array_values(array_unique($custom_order));
462 $custom_order_lookup = array_flip($custom_order);
463 $order_setting = isset($taxonomy_settings[$taxonomy]['order']) ? $taxonomy_settings[$taxonomy]['order'] : 'desc';
464 $orderby_setting = isset($taxonomy_settings[$taxonomy]['orderby']) ? $taxonomy_settings[$taxonomy]['orderby'] : 'ID';
465 $use_custom_order = ($orderby_setting === 'taxopress_term_order');
466 $display_custom_order = ($order_setting === 'desc') ? array_reverse($custom_order) : $custom_order;
467
468 if ($use_custom_order && empty($search) && empty($selected_post_type)) {
469 $taxonomy_count = $this->count_taxonomy_terms($taxonomy, '', '');
470 $ordered_count = min(count($display_custom_order), $taxonomy_count);
471 $new_count = max(0, $taxonomy_count - $ordered_count);
472
473 if ($remaining_offset < $ordered_count) {
474 $ordered_term_ids = array_slice($display_custom_order, $remaining_offset, $remaining_number);
475 $this->append_term_refs($term_refs, $taxonomy, $ordered_term_ids);
476 $remaining_number -= count($ordered_term_ids);
477 $remaining_offset = 0;
478 } elseif ($remaining_offset >= $ordered_count) {
479 $remaining_offset -= $ordered_count;
480 }
481
482 if ($remaining_number > 0 && $remaining_offset < $new_count) {
483 $new_number = min($remaining_number, $new_count - $remaining_offset);
484 $all_term_ids = get_terms([
485 'taxonomy' => [$taxonomy],
486 'hide_empty' => false,
487 'pad_counts' => false,
488 'hierarchical' => false,
489 'update_term_meta_cache' => false,
490 'fields' => 'ids',
491 ]);
492 $new_term_ids = is_wp_error($all_term_ids)
493 ? []
494 : array_values(array_diff(array_map('intval', (array) $all_term_ids), $custom_order));
495 $new_term_ids = array_slice($new_term_ids, $remaining_offset, $new_number);
496
497 if (!empty($new_term_ids)) {
498 if ($order_setting === 'desc') {
499 $new_term_ids = array_reverse($new_term_ids);
500 }
501 $this->append_term_refs($term_refs, $taxonomy, $new_term_ids);
502 $remaining_number -= count($new_term_ids);
503 }
504
505 $remaining_offset = 0;
506 } elseif ($remaining_offset >= $new_count) {
507 $remaining_offset -= $new_count;
508 }
509
510 continue;
511 }
512
513 $terms_attr = [
514 'taxonomy' => [$taxonomy],
515 'post_types' => $selected_post_type,
516 'hide_empty' => false,
517 'pad_counts' => false,
518 'hierarchical' => false,
519 'update_term_meta_cache' => false,
520 'fields' => 'ids',
521 'search' => $search,
522 ];
523
524 if (!$use_custom_order) {
525 $terms_attr['orderby'] = $orderby_setting;
526 $terms_attr['order'] = $order_setting;
527 }
528
529 $taxonomy_count = $this->count_taxonomy_terms($taxonomy, $selected_post_type, $search);
530 if ($remaining_offset >= $taxonomy_count) {
531 $remaining_offset -= $taxonomy_count;
532 continue;
533 }
534
535 $terms_attr['offset'] = $remaining_offset;
536 $terms_attr['number'] = $remaining_number;
537
538 // Manual ordering with active filters still needs ID-level filtering, but never hydrates full term objects.
539 $taxonomy_term_ids = get_terms($terms_attr);
540
541 if (empty($taxonomy_term_ids) || is_wp_error($taxonomy_term_ids)) {
542 continue;
543 }
544
545 $taxonomy_term_ids = array_map('intval', (array) $taxonomy_term_ids);
546
547 if ($use_custom_order) {
548 $terms_by_id = array_flip($taxonomy_term_ids);
549 $ordered_term_ids = [];
550 $new_term_ids = [];
551
552 // Ordered terms
553 foreach ($display_custom_order as $term_id) {
554 if (isset($terms_by_id[$term_id])) {
555 $ordered_term_ids[] = $term_id;
556 }
557 }
558
559 // Terms not in custom order
560 foreach ($taxonomy_term_ids as $term_id) {
561 if (!isset($custom_order_lookup[$term_id])) {
562 $new_term_ids[] = $term_id;
563 }
564 }
565
566 // Merge: custom-ordered terms first, then new/unordered terms.
567 $taxonomy_term_ids = array_merge($ordered_term_ids, $new_term_ids);
568 } else {
569 if ($orderby_setting === 'random') {
570 shuffle($taxonomy_term_ids);
571 if ($order_setting === 'desc') {
572 $taxonomy_term_ids = array_reverse($taxonomy_term_ids);
573 }
574 }
575 }
576
577 $this->append_term_refs($term_refs, $taxonomy, $taxonomy_term_ids);
578 $remaining_number -= count($taxonomy_term_ids);
579 $remaining_offset = 0;
580 }
581
582 $terms = $this->get_terms_from_refs($term_refs);
583
584 // HIERARCHY SUPPORT
585 if (!empty($selected_taxonomy)) {
586 $terms = $this->taxopress_arrange_terms_hierarchically($terms);
587 } else {
588 $terms = $this->taxopress_flatten_terms_tree($terms);
589 }
590
591 return $this->prioritize_copied_term($terms);
592 }
593
594 // If no manual ordering, use the efficient all-in-one get_terms
595 $terms_attr = [
596 'taxonomy' => $taxonomies,
597 'post_types' => $selected_post_type,
598 'orderby' => $orderby_setting,
599 'order' => $order_setting,
600 'search' => $search,
601 'hide_empty' => false,
602 'pad_counts' => false,
603 'hierarchical' => false,
604 'update_term_meta_cache' => true,
605 ];
606 if ($count || $show_all_terms_in_taxonomy) {
607 $terms_attr['number'] = 0;
608 } else {
609 $terms_attr['offset'] = $offset;
610 $terms_attr['number'] = $items_per_page;
611 }
612
613 $terms = get_terms($terms_attr);
614
615 if (empty($terms) || is_wp_error($terms)) {
616 return [];
617 }
618
619 if (!empty($requested_orderby) && $requested_orderby === 'taxonomy') {
620 usort($terms, function ($a, $b) use ($order_setting) {
621 $cmp = strcmp($a->taxonomy, $b->taxonomy);
622 return ($order_setting === 'desc') ? -$cmp : $cmp;
623 });
624 }
625
626 if ($orderby_setting === 'random') {
627 shuffle($terms);
628 if ($order_setting === 'desc') {
629 $terms = array_reverse($terms);
630 }
631 }
632
633 // HIERARCHY SUPPORT
634 if (!empty($selected_taxonomy)) {
635 $terms = $this->taxopress_arrange_terms_hierarchically($terms);
636 } else {
637 $terms = $this->taxopress_flatten_terms_tree($terms);
638 }
639
640 return $this->prioritize_copied_term($terms);
641 }
642
643 /**
644 * Retrieve st_Terms data from the database
645 *
646 * @param int $per_page
647 * @param int $page_number
648 *
649 * @return mixed
650 */
651 public function get_st_Terms()
652 {
653 return $this->get_all_terms();
654 }
655
656 /**
657 * Returns the count of records in the database.
658 *
659 * @return null|string
660 */
661 public function record_count()
662 {
663 global $wpdb;
664
665 $filters = $this->get_current_term_filters();
666
667 if (empty($filters['taxonomies'])) {
668 return 0;
669 }
670
671 $join = "INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
672 $where = [];
673 $query_args = [];
674
675 $taxonomy_placeholders = implode(', ', array_fill(0, count($filters['taxonomies']), '%s'));
676 $where[] = "tt.taxonomy IN ({$taxonomy_placeholders})";
677 $query_args = array_merge($query_args, $filters['taxonomies']);
678
679 if (!empty($filters['search'])) {
680 $where[] = 't.name LIKE %s';
681 $query_args[] = '%' . $wpdb->esc_like($filters['search']) . '%';
682 }
683
684 if (!empty($filters['selected_post_type'])) {
685 $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
686 $join .= " INNER JOIN {$wpdb->posts} AS p ON p.ID = tr.object_id";
687 $where[] = 'p.post_type = %s';
688 $query_args[] = $filters['selected_post_type'];
689 }
690
691 $where_sql = implode(' AND ', $where);
692 $sql = "SELECT COUNT(DISTINCT tt.term_taxonomy_id) FROM {$wpdb->terms} AS t {$join} WHERE {$where_sql}";
693 $cache_key = 'taxopress_terms_count_' . md5($sql . wp_json_encode($query_args));
694 $cached_count = wp_cache_get($cache_key, 'taxopress_terms');
695
696 if ($cached_count !== false) {
697 return (int) $cached_count;
698 }
699
700 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Cached count query avoids loading term objects on large sites.
701 $total_items = (int) $wpdb->get_var($wpdb->prepare($sql, $query_args));
702 wp_cache_set($cache_key, $total_items, 'taxopress_terms', 60);
703
704 return $total_items;
705 }
706
707 /**
708 * Show single row item
709 *
710 * @param array $item
711 */
712 public function single_row($item)
713 {
714 $class = ['st-terms-tr'];
715 $id = 'term-' . $item->term_id . '';
716 echo sprintf('<tr id="%s" class="%s">', esc_attr($id), esc_attr(implode(' ', $class)));
717 $this->single_row_columns($item);
718 echo '</tr>';
719 }
720
721 /**
722 * Associative array of columns
723 *
724 * @return array
725 */
726 public function get_columns()
727 {
728
729 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
730 if (!empty($_REQUEST['taxopress_terms_taxonomy'])) {
731 return [
732 'name' => esc_html__('Title', 'simple-tags'),
733 'description' => esc_html__('Description', 'simple-tags'),
734 'count' => esc_html__('Count', 'simple-tags'),
735 ];
736 }
737 $columns = [
738 'cb' => '<input type="checkbox" />',
739 'name' => esc_html__('Title', 'simple-tags'),
740 'slug' => esc_html__('Slug', 'simple-tags'),
741 'description' => esc_html__('Description', 'simple-tags'),
742 'taxonomy' => esc_html__('Taxonomy', 'simple-tags'),
743 'posttypes' => esc_html__('Post Types', 'simple-tags'),
744 'taxopress_custom_url' => esc_html__('Custom URL', 'simple-tags'),
745 'synonyms' => esc_html__('Synonyms', 'simple-tags'),
746 'linked_terms' => esc_html__('Linked Terms', 'simple-tags'),
747 'hidden_status' => esc_html__('Status', 'simple-tags'),
748 'count' => esc_html__('Count', 'simple-tags')
749 ];
750
751 if (!taxopress_is_pro_version()) {
752 unset($columns['synonyms']);
753 unset($columns['linked_terms']);
754 }
755
756 if (!(int) SimpleTags_Plugin::get_option_value('enable_hidden_terms')) {
757 unset($columns['hidden_status']);
758 }
759
760 return $columns;
761 }
762
763 /**
764 * Columns to make sortable.
765 *
766 * @return array
767 */
768 protected function get_sortable_columns()
769 {
770 $sortable_columns = [
771 'name' => ['name', true],
772 'slug' => ['slug', true],
773 'taxonomy' => ['taxonomy', true],
774 'count' => ['count', true],
775 ];
776
777 return $sortable_columns;
778 }
779
780 /**
781 * Render the bulk edit checkbox
782 *
783 * @param array $item
784 *
785 * @return string
786 */
787 public function column_cb($item)
788 {
789 return sprintf('<input type="checkbox" name="%1$s[]" value="%2$s" />', 'taxopress_terms', $item->term_id);
790 }
791
792 /**
793 * Get the bulk actions to show in the top page dropdown
794 *
795 * @return array
796 */
797 protected function get_bulk_actions()
798 {
799 $actions = [
800 'taxopress-terms-delete-terms' => esc_html__('Delete', 'simple-tags'),
801 'taxopress-terms-copy-terms' => esc_html__('Copy', 'simple-tags')
802 ];
803
804 return $actions;
805 }
806
807 /**
808 * Add custom filter to tablenav
809 *
810 * @param string $which
811 */
812 protected function extra_tablenav($which)
813 {
814 // Hide filters if taxopress_show_all=1
815 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
816 if ('top' === $which && empty($_REQUEST['taxopress_show_all'])) {
817 $post_types = get_post_types(['public' => true], 'objects');
818
819 $taxonomies = get_all_taxopress_taxonomies_request();
820
821 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
822 $selected_post_type = (!empty($_REQUEST['terms_filter_post_type'])) ? sanitize_text_field(wp_unslash($_REQUEST['terms_filter_post_type'])) : '';
823 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
824 $selected_taxonomy = (!empty($_REQUEST['terms_filter_taxonomy'])) ? sanitize_text_field(wp_unslash($_REQUEST['terms_filter_taxonomy'])) : '';
825 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
826 $selected_post = (!empty($_REQUEST['taxopress_destination_post_type'])) ? sanitize_text_field(wp_unslash($_REQUEST['taxopress_destination_post_type'])) : '';
827
828 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying GET parameter for taxonomy type
829 $selected_option = 'public';
830 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
831 if (isset($_GET['taxonomy_type']) && $_GET['taxonomy_type'] === 'all') {
832 $selected_option = 'all';
833 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
834 } elseif (isset($_GET['taxonomy_type']) && $_GET['taxonomy_type'] === 'private') {
835 $selected_option = 'private';
836 }
837 ?>
838
839 <div class="alignleft actions autoterms-terms-table-copy" id="taxopress-copy-selection-boxes" style="display: none;">
840 <select class="auto-terms-terms-copy-select" name="taxopress_destination_taxonomy" id="terms_copy_select_destination_taxonomy">
841 <option value=""><?php esc_html_e('Select Destination Taxonomy', 'simple-tags'); ?></option> <?php
842 foreach ($taxonomies as $taxonomy) {
843 echo '<option value="' . esc_attr($taxonomy->name) . '">' . esc_html($taxonomy->labels->name) . '</option>';
844 } ?>
845 </select>
846
847 <select class="auto-terms-terms-copy-select taxopress-post-search" name="taxopress_destination_post_type" id="terms_copy_select_destination_post" data-placeholder="<?php esc_attr_e('Search posts', 'simple-tags'); ?>" data-allow-clear="true" data-nonce="<?php echo esc_attr(wp_create_nonce('taxopress-post-search')); ?>" data-post-types="<?php echo esc_attr($selected_post_type); ?>">
848 <?php
849 $post_type_label = !empty($selected_post_type) ? esc_html($selected_post_type) : esc_html__('post type', 'simple-tags');
850 ?>
851 <option value=""><?php printf(esc_html__('Select Destination %s', 'simple-tags'), esc_html($post_type_label)); ?></option>
852 <option value="all" <?php selected($selected_post, 'all'); ?>><?php printf(esc_html__('All %s', 'simple-tags'), esc_html($post_type_label)); ?></option>
853 </select>
854 </div>
855 <div class="alignleft actions autoterms-terms-table-filter">
856
857 <select class="auto-terms-terms-filter-select" name="terms_filter_select_post_type" id="terms_filter_select_post_type">
858 <option value=""><?php esc_html_e('Post type', 'simple-tags'); ?></option>
859 <?php
860 foreach ($post_types as $post_type) {
861 echo '<option value="' . esc_attr($post_type->name) . '" ' . selected($selected_post_type, $post_type->name, false) . '>' . esc_html($post_type->label) . '</option>';
862 }
863 ?>
864 </select>
865
866 <select class="auto-terms-terms-filter-select" name="terms_filter_select_taxonomy" id="terms_filter_select_taxonomy">
867 <option value=""><?php esc_html_e('Taxonomy', 'simple-tags'); ?></option>
868 <?php
869 foreach ($taxonomies as $taxonomy) {
870 echo '<option value="' . esc_attr($taxonomy->name) . '" ' . selected($selected_taxonomy, $taxonomy->name, false) . '>' . esc_html($taxonomy->labels->name) . '</option>';
871 }
872 ?>
873 </select>
874
875 <select class="auto-terms-terms-filter-select" name="terms_filter_select_taxonomy_type" id="terms_filter_select_taxonomy_type">
876 <option value="all" <?php echo($selected_option === 'all' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('All Taxonomies', 'simple-tags'); ?></option>
877 <option value="public" <?php echo($selected_option === 'public' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('Public Taxonomies', 'simple-tags'); ?></option>
878 <option value="private" <?php echo($selected_option === 'private' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('Private Taxonomies', 'simple-tags'); ?></option>
879 </select>
880
881 <a href="javascript:void(0)" class="taxopress-terms-tablenav-filter button"><?php esc_html_e('Filter', 'simple-tags'); ?></a>
882
883 </div>
884 <?php
885 }
886 }
887
888 /**
889 * Process bulk actions
890 */
891 public function process_bulk_action()
892 {
893
894 $query_arg = '_wpnonce';
895 $action = 'bulk-' . $this->_args['plural'];
896 $checked = isset($_REQUEST[$query_arg]) ? wp_verify_nonce(sanitize_key(wp_unslash($_REQUEST[$query_arg])), $action) : false;
897
898 if (!$checked || !current_user_can('simple_tags')) {
899 return;
900 }
901
902 if ($this->current_action() === 'taxopress-terms-delete-terms') {
903 $taxopress_terms = !empty($_REQUEST['taxopress_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_REQUEST['taxopress_terms'])) : [];
904 if (!empty($taxopress_terms)) {
905 $deleted_terms_by_taxonomy = [];
906 foreach ($taxopress_terms as $taxopress_term) {
907 $term = get_term($taxopress_term);
908 if (!$term || is_wp_error($term)) {
909 continue;
910 }
911
912 $deleted_terms_by_taxonomy[$term->taxonomy][] = (int) $term->term_id;
913 wp_delete_term($term->term_id, $term->taxonomy);
914 }
915 $this->remove_terms_from_manual_order($deleted_terms_by_taxonomy);
916
917 if (count($taxopress_terms) > 1) {
918 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
919 echo taxopress_admin_notices_helper(esc_html__('Terms deleted successfully.', 'simple-tags'), false);
920 } else {
921 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
922 echo taxopress_admin_notices_helper(esc_html__('Term deleted successfully.', 'simple-tags'), false);
923 }
924 }
925 }
926 if ($this->current_action() === 'taxopress-terms-copy-terms') {
927 $taxopress_terms = !empty($_REQUEST['taxopress_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_REQUEST['taxopress_terms'])) : [];
928 $destination_taxonomy = !empty($_REQUEST['taxopress_destination_taxonomy']) ? sanitize_text_field(wp_unslash($_REQUEST['taxopress_destination_taxonomy'])) : '';
929 $destination_post = !empty($_REQUEST['taxopress_destination_post_type']) ? sanitize_text_field(wp_unslash($_REQUEST['taxopress_destination_post_type'])) : '';
930
931 if (!empty($taxopress_terms) && !empty($destination_taxonomy)) {
932 foreach ($taxopress_terms as $taxopress_term) {
933 $term = get_term($taxopress_term);
934 wp_insert_term($term->name, $destination_taxonomy, [
935 'slug' => $term->slug,
936 'description' => $term->description,
937 ]);
938 if (!empty($destination_post) && $destination_post !== 'all') {
939 wp_set_object_terms($destination_post, [$term->term_id], $destination_taxonomy, true);
940 }
941
942 if ($destination_post === 'all') {
943 foreach (taxopress_get_post_ids_for_terms_action() as $post_id) {
944 wp_set_object_terms($post_id, [$term->term_id], $destination_taxonomy, true);
945 }
946 }
947 }
948 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
949 echo taxopress_admin_notices_helper(esc_html__('Term(s) copied successfully.', 'simple-tags'), true);
950 }
951 }
952 }
953
954 protected function column_taxopress_custom_url($item)
955 {
956 $taxopress_custom_url = get_term_meta($item->term_id, 'taxopress_custom_url', true);
957 return (!empty($taxopress_custom_url) && filter_var($taxopress_custom_url, FILTER_VALIDATE_URL))
958 ? sprintf('<a href="%s" target="_blank">%s</a>', esc_url($taxopress_custom_url), esc_html($taxopress_custom_url))
959 : '-';
960 }
961
962 protected function column_hidden_status($item)
963 {
964 $hidden_terms = get_transient('taxopress_hidden_terms_' . $item->taxonomy);
965
966 if (!empty($hidden_terms) && in_array($item->term_id, $hidden_terms)) {
967 return esc_html__('Hidden', 'simple-tags');
968 }
969
970 return esc_html__('Live', 'simple-tags');
971 }
972
973 /**
974 * Render a column when no column specific method exist.
975 *
976 * @param array $item
977 * @param string $column_name
978 *
979 * @return mixed
980 */
981 public function column_default($item, $column_name)
982 {
983 return !empty($item->$column_name) ? $item->$column_name : '&mdash;';
984 }
985
986 /** Text displayed when no stterm data is available */
987 public function no_items()
988 {
989 esc_html_e('No terms found.', 'simple-tags');
990 }
991
992 /**
993 * Displays the search box.
994 *
995 * @param string $text The 'submit' button label.
996 * @param string $input_id ID attribute value for the search input field.
997 *
998 *
999 */
1000 public function search_box($text, $input_id)
1001 {
1002 // Hide search box if taxopress_show_all=1
1003 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
1004 if ((!empty($_REQUEST['taxopress_show_all']) && $_REQUEST['taxopress_show_all'] == '1')) {
1005 return;
1006 }
1007
1008 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
1009 if (empty($_REQUEST['s']) && !$this->has_items()) {
1010 //return;
1011 }
1012
1013 $input_id = $input_id . '-search-input';
1014
1015 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1016 if (!empty($_REQUEST['orderby'])) {
1017 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1018 echo '<input type="hidden" name="orderby" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['orderby']))) . '" />';
1019 }
1020 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1021 if (!empty($_REQUEST['order'])) {
1022 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1023 echo '<input type="hidden" name="order" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['order']))) . '" />';
1024 }
1025 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1026 if (!empty($_REQUEST['page'])) {
1027 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1028 echo '<input type="hidden" name="page" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['page']))) . '" />';
1029 }
1030
1031 $custom_filters = ['terms_filter_post_type', 'terms_filter_taxonomy', 'taxonomy_type'];
1032
1033 foreach ($custom_filters as $custom_filter) {
1034 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
1035 $filter_value = !empty($_REQUEST[$custom_filter]) ? sanitize_text_field(wp_unslash($_REQUEST[$custom_filter])) : '';
1036 echo '<input type="hidden" name="' . esc_attr($custom_filter) . '" value="' . esc_attr($filter_value) . '" />';
1037 }
1038 ?>
1039 <p class="search-box">
1040 <label class="screen-reader-text" for="<?php echo esc_attr($input_id); ?>"><?php echo esc_html($text); ?>:</label>
1041 <input type="search" id="<?php echo esc_attr($input_id); ?>" name="s" value="<?php _admin_search_query(); ?>" />
1042 <?php submit_button($text, '', '', false, ['id' => 'taxopress-terms-search-submit']); ?>
1043 </p>
1044 <?php
1045 }
1046
1047 /**
1048 * Sets up the items (roles) to list.
1049 */
1050 public function prepare_items()
1051 {
1052
1053 $this->_column_headers = $this->get_column_info();
1054 $this->process_bulk_action();
1055
1056 /**
1057 * First, lets decide how many records per page to show
1058 */
1059 $per_page = $this->get_items_per_page('st_terms_per_page', 20);
1060
1061 /**
1062 * Fetch the data
1063 */
1064 $data = $this->get_st_Terms();
1065 if (!empty($data)) {
1066 update_termmeta_cache(wp_list_pluck($data, 'term_id'));
1067 }
1068
1069 /**
1070 * Pagination.
1071 */
1072 $current_page = $this->get_pagenum();
1073 $total_items = $this->record_count();
1074
1075 /**
1076 * Now we can add the data to the items property, where it can be used by the rest of the class.
1077 */
1078 $this->items = $data;
1079
1080 /**
1081 * We also have to register our pagination options & calculations.
1082 */
1083 $this->set_pagination_args([
1084 'total_items' => $total_items, //calculate the total number of items
1085 'per_page' => $per_page, //determine how many items to show on a page
1086 'total_pages' => ceil($total_items / $per_page) //calculate the total number of pages
1087 ]);
1088 }
1089
1090 /**
1091 * Generates and display row actions links for the list table.
1092 *
1093 * @param object $item The item being acted upon.
1094 * @param string $column_name Current column name.
1095 * @param string $primary Primary column name.
1096 *
1097 * @return string The row actions HTML, or an empty string if the current column is the primary column.
1098 */
1099 protected function handle_row_actions($item, $column_name, $primary)
1100 {
1101 $taxonomy = get_taxonomy($item->taxonomy);
1102
1103 //Build row actions
1104 $actions = [];
1105
1106 if (current_user_can('edit_term', $item->term_id)) {
1107 $actions['edit'] = sprintf(
1108 '<a href="%s">%s</a>',
1109 add_query_arg(
1110 [
1111 'taxonomy' => $item->taxonomy,
1112 'tag_ID' => $item->term_id,
1113 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
1114 ],
1115 admin_url('term.php')
1116 ),
1117 esc_html__('Edit', 'simple-tags')
1118 );
1119 }
1120
1121 // Only add other actions if not viewing via taxopress_terms_taxonomy
1122 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
1123 if (empty($_REQUEST['taxopress_terms_taxonomy'])) {
1124 if (current_user_can('edit_term', $item->term_id)) {
1125 $actions['inline hide-if-no-js'] = sprintf(
1126 '<button type="button" class="button-link editinline" aria-label="%s" aria-expanded="false" data-taxonomy="' . $taxonomy->name . '" data-term-id="' . $item->term_id . '">%s</button>',
1127 /* translators: %s: Taxonomy term name. */
1128 esc_attr(sprintf(esc_html__('Quick edit &#8220;%s&#8221; inline', 'simple-tags'), $item->name)),
1129 esc_html__('Quick&nbsp;Edit', 'simple-tags')
1130 );
1131 }
1132
1133 if (current_user_can('edit_term', $item->term_id)) {
1134 $actions['remove_posts'] = sprintf(
1135 '<a href="%s">%s</a>',
1136 add_query_arg(
1137 taxopress_get_terms_screen_query_args([
1138 'action' => 'taxopress-remove-from-posts',
1139 'taxopress_terms' => esc_attr($item->term_id),
1140 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1141 ]),
1142 admin_url('admin.php')
1143 ),
1144 esc_html__('Remove From All Posts', 'simple-tags')
1145 );
1146 }
1147
1148 if (current_user_can('delete_term', $item->term_id)) {
1149 $actions['delete'] = sprintf(
1150 '<a href="%s" class="delete-terms">%s</a>',
1151 add_query_arg(
1152 taxopress_get_terms_screen_query_args([
1153 'action' => 'taxopress-delete-terms',
1154 'taxopress_terms' => esc_attr($item->term_id),
1155 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1156 ]),
1157 admin_url('admin.php')
1158 ),
1159 esc_html__('Delete', 'simple-tags')
1160 );
1161 }
1162
1163 if (is_taxonomy_viewable($item->taxonomy)) {
1164 $actions['view'] = sprintf(
1165 '<a href="%s">%s</a>',
1166 get_term_link($item->term_id),
1167 esc_html__('View', 'simple-tags')
1168 );
1169 }
1170
1171 $actions['copy_term'] = sprintf(
1172 '<a href="%s">%s</a>',
1173 add_query_arg(
1174 taxopress_get_terms_screen_query_args([
1175 'action' => 'taxopress-copy-term',
1176 'taxopress_terms' => esc_attr($item->term_id),
1177 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1178 ]),
1179 admin_url('admin.php')
1180 ),
1181 esc_html__('Copy', 'simple-tags')
1182 );
1183 $actions = apply_filters('taxopress_terms_row_actions', $actions, $item);
1184 }
1185 return $column_name === $primary ? $this->row_actions($actions, false) : '';
1186 }
1187
1188 /**
1189 * Method for synonyms column
1190 *
1191 * @param array $item
1192 *
1193 * @return string
1194 */
1195 protected function column_synonyms($item)
1196 {
1197 $term_synonyms = taxopress_get_term_synonyms($item->term_id);
1198 if (!empty($term_synonyms)) {
1199 return join(', ', $term_synonyms);
1200 } else {
1201 return '-';
1202 }
1203 }
1204
1205 /**
1206 * Method for linked_terms column
1207 *
1208 * @param array $item
1209 *
1210 * @return string
1211 */
1212 protected function column_linked_terms($item)
1213 {
1214 $term_linked_terms = taxopress_get_linked_terms($item->term_id);
1215 if (!empty($term_linked_terms)) {
1216 $term_linked_term_names = [];
1217 foreach ($term_linked_terms as $term_linked_term) {
1218 $linked_term_data = taxopress_get_linked_term_data($term_linked_term, $item->term_id);
1219 $term_linked_term_names[] = $linked_term_data->term_name . ' (' . $linked_term_data->term_taxonomy . ')';
1220 }
1221 return join(', ', $term_linked_term_names);
1222 } else {
1223 return '-';
1224 }
1225 }
1226
1227 /**
1228 * Method for name column
1229 *
1230 * @param array $item
1231 *
1232 * @return string
1233 */
1234 protected function column_name($item)
1235 {
1236 $taxonomy = get_taxonomy($item->taxonomy);
1237
1238 // Add dashes for hierarchy
1239 $depth = isset($item->taxopress_depth) ? (int)$item->taxopress_depth : 0;
1240 $dash = $depth > 0 ? str_repeat('&mdash; ', $depth) : '';
1241
1242 $title = sprintf(
1243 '<a href="%1$s"><strong><span class="row-title">%2$s%3$s</span></strong></a>',
1244 add_query_arg(
1245 [
1246 'taxonomy' => $item->taxonomy,
1247 'tag_ID' => $item->term_id,
1248 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
1249 ],
1250 admin_url('term.php')
1251 ),
1252 $dash,
1253 esc_html($item->name)
1254 );
1255
1256 $title .= ' <span class="taxopress-term-spinner" style="display:none;vertical-align:middle;"><span class="spinner is-active"></span></span>';
1257
1258 // Use the already-loaded term object and apply edit filters without an extra uncached DB query.
1259 $qe_data = sanitize_term(clone $item, $item->taxonomy, 'edit');
1260
1261 $title .= '<div class="hidden" id="inline_' . $qe_data->term_id . '">';
1262 $title .= '<div class="taxonomy">' . $item->taxonomy . '</div>';
1263 $title .= '<div class="name">' . $qe_data->name . '</div>';
1264
1265 $title .= '<div class="slug">' . apply_filters('editable_slug', $qe_data->slug, $qe_data) . '</div>';
1266 $title .= '<div class="parent">' . $qe_data->parent . '</div>
1267 </div>';
1268
1269 return $title;
1270 }
1271
1272 /**
1273 * The action column
1274 *
1275 * @param $item
1276 *
1277 * @return string
1278 */
1279 protected function column_slug($item)
1280 {
1281 return !empty($item->slug) ? $item->slug : '&mdash;';
1282 }
1283
1284 /**
1285 * The action column
1286 *
1287 * @param $item
1288 *
1289 * @return string
1290 */
1291 protected function column_posttypes($item)
1292 {
1293 $posttype = '';
1294 $sn = 0;
1295 $taxonomy = get_taxonomy($item->taxonomy);
1296 foreach ($taxonomy->object_type as $objecttype) {
1297 $sn++;
1298 $post_type_object = get_post_type_object($objecttype);
1299 if (is_object($post_type_object)) {
1300 $posttype .= $post_type_object->label;
1301 if ($sn < count($taxonomy->object_type)) {
1302 $posttype .= ', ';
1303 }
1304 }
1305 }
1306
1307 return $posttype;
1308 }
1309
1310 /**
1311 * The action column
1312 *
1313 * @param $item
1314 *
1315 * @return string
1316 */
1317 protected function column_count($item)
1318 {
1319 $term_counts = isset($item->count) ? (int) $item->count : 0;
1320
1321 return sprintf(
1322 '<a href="%s" class="">%s</a>',
1323 add_query_arg(
1324 [
1325 'page' => 'st_posts',
1326 'posts_term_filter' => (int) $item->term_id,
1327 ],
1328 admin_url('admin.php')
1329 ),
1330 number_format_i18n($term_counts)
1331 );
1332 }
1333
1334 /**
1335 * The action column
1336 *
1337 * @param $item
1338 *
1339 * @return string
1340 */
1341 protected function column_description($item)
1342 {
1343
1344 return term_description($item->term_id);
1345 }
1346
1347 /**
1348 * Method for taxonomy column
1349 *
1350 * @param array $item
1351 *
1352 * @return string
1353 */
1354 protected function column_taxonomy($item)
1355 {
1356 $taxonomy = get_taxonomy($item->taxonomy);
1357
1358 if ($taxonomy) {
1359 $return = sprintf(
1360 '<a href="%1$s">%2$s</a>',
1361 add_query_arg(
1362 [
1363 'page' => 'st_taxonomies',
1364 'add' => 'taxonomy',
1365 'action' => 'edit',
1366 'taxopress_taxonomy' => $taxonomy->name,
1367 ],
1368 taxopress_admin_url('admin.php')
1369 ),
1370 esc_html($taxonomy->labels->name)
1371 );
1372 } else {
1373 $return = '&mdash;';
1374 }
1375
1376 return $return;
1377 }
1378
1379 /**
1380 * Outputs the hidden row displayed when inline editing
1381 *
1382 * @since 3.1.0
1383 */
1384 public function inline_edit()
1385 {
1386 ?>
1387
1388 <form method="get">
1389 <table style="display: none">
1390 <tbody id="inlineedit">
1391
1392 <tr id="inline-edit" class="inline-edit-row" style="display: none">
1393 <td colspan="<?php echo esc_attr($this->get_column_count()); ?>" class="colspanchange">
1394
1395 <fieldset>
1396 <legend class="inline-edit-legend"><?php esc_html_e('Quick Edit', 'simple-tags'); ?></legend>
1397 <div class="inline-edit-col">
1398 <label>
1399 <span class="title"><?php _ex('Name', 'term name', 'simple-tags'); ?></span>
1400 <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span>
1401 </label>
1402
1403 <label>
1404 <span class="title"><?php esc_html_e('Slug', 'simple-tags'); ?></span>
1405 <span class="input-text-wrap"><input type="text" name="slug" class="ptitle" value="" /></span>
1406 </label>
1407 <label>
1408 <span class="taxonomy"><?php _ex('Taxonomy', 'term name', 'simple-tags'); ?></span>
1409
1410 <?php $taxonomies = get_all_taxopress_taxonomies(); ?>
1411 <select class="input-text-wrap edit-tax edit_taxonomy" name="edit_taxonomy">
1412 <?php
1413 foreach ($taxonomies as $taxonomy) {
1414 echo '<option value="' . esc_attr($taxonomy->name) . '">' . esc_html($taxonomy->labels->name) . '</option>';
1415 }
1416 ?>
1417 </select>
1418 </label>
1419 </div>
1420 </fieldset>
1421
1422 <div class="inline-edit-save submit">
1423 <button type="button" class="cancel button alignleft"><?php esc_html_e('Cancel', 'simple-tags'); ?></button>
1424 <button type="button" class="taxopress-save button button-primary alignright"><?php esc_html_e('Update', 'simple-tags'); ?></button>
1425 <span class="spinner"></span>
1426
1427 <?php wp_nonce_field('taxinlineeditnonce', '_inline_edit', false); ?>
1428 <br class="clear" />
1429
1430 <div class="notice notice-error notice-alt inline hidden taxopress-notice">
1431 <p class="error"></p>
1432 </div>
1433 </div>
1434
1435 </td>
1436 </tr>
1437
1438 </tbody>
1439 </table>
1440 </form>
1441 <?php
1442 }
1443 }
1444