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 / terms-table.php

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

1,482 lines 58.5 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 if (!taxopress_current_user_can_for_taxonomy($term->taxonomy, 'delete_terms')) {
913 continue;
914 }
915
916 $deleted_terms_by_taxonomy[$term->taxonomy][] = (int) $term->term_id;
917 wp_delete_term($term->term_id, $term->taxonomy);
918 }
919 $this->remove_terms_from_manual_order($deleted_terms_by_taxonomy);
920
921 if (count($taxopress_terms) > 1) {
922 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
923 echo taxopress_admin_notices_helper(esc_html__('Terms deleted successfully.', 'simple-tags'), false);
924 } else {
925 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
926 echo taxopress_admin_notices_helper(esc_html__('Term deleted successfully.', 'simple-tags'), false);
927 }
928 }
929 }
930 if ($this->current_action() === 'taxopress-terms-copy-terms') {
931 $taxopress_terms = !empty($_REQUEST['taxopress_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_REQUEST['taxopress_terms'])) : [];
932 $destination_taxonomy = !empty($_REQUEST['taxopress_destination_taxonomy']) ? sanitize_key(wp_unslash($_REQUEST['taxopress_destination_taxonomy'])) : '';
933 $destination_post = !empty($_REQUEST['taxopress_destination_post_type']) ? sanitize_text_field(wp_unslash($_REQUEST['taxopress_destination_post_type'])) : '';
934
935 if (
936 !empty($taxopress_terms)
937 && !empty($destination_taxonomy)
938 && taxopress_current_user_can_for_taxonomy(
939 $destination_taxonomy,
940 ['edit_terms', 'assign_terms']
941 )
942 ) {
943 foreach ($taxopress_terms as $taxopress_term) {
944 $term = get_term($taxopress_term);
945 if (
946 !$term
947 || is_wp_error($term)
948 || !taxopress_current_user_can_for_taxonomy($term->taxonomy, 'edit_terms')
949 ) {
950 continue;
951 }
952
953 $inserted_term = wp_insert_term($term->name, $destination_taxonomy, [
954 'slug' => $term->slug,
955 'description' => $term->description,
956 ]);
957 if (is_wp_error($inserted_term)) {
958 $existing_term = term_exists($term->slug, $destination_taxonomy);
959 $destination_term_id = is_array($existing_term) ? (int) $existing_term['term_id'] : (int) $existing_term;
960 } else {
961 $destination_term_id = (int) $inserted_term['term_id'];
962 }
963
964 if (
965 $destination_term_id
966 && !empty($destination_post)
967 && $destination_post !== 'all'
968 && current_user_can('edit_post', (int) $destination_post)
969 && is_object_in_taxonomy(get_post_type((int) $destination_post), $destination_taxonomy)
970 ) {
971 wp_set_object_terms((int) $destination_post, [$destination_term_id], $destination_taxonomy, true);
972 }
973
974 if ($destination_term_id && $destination_post === 'all') {
975 foreach (taxopress_get_post_ids_for_terms_action() as $post_id) {
976 if (
977 !current_user_can('edit_post', $post_id)
978 || !is_object_in_taxonomy(get_post_type($post_id), $destination_taxonomy)
979 ) {
980 continue;
981 }
982 wp_set_object_terms($post_id, [$destination_term_id], $destination_taxonomy, true);
983 }
984 }
985 }
986 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
987 echo taxopress_admin_notices_helper(esc_html__('Term(s) copied successfully.', 'simple-tags'), true);
988 }
989 }
990 }
991
992 protected function column_taxopress_custom_url($item)
993 {
994 $taxopress_custom_url = get_term_meta($item->term_id, 'taxopress_custom_url', true);
995 return (!empty($taxopress_custom_url) && filter_var($taxopress_custom_url, FILTER_VALIDATE_URL))
996 ? sprintf('<a href="%s" target="_blank">%s</a>', esc_url($taxopress_custom_url), esc_html($taxopress_custom_url))
997 : '-';
998 }
999
1000 protected function column_hidden_status($item)
1001 {
1002 $hidden_terms = get_transient('taxopress_hidden_terms_' . $item->taxonomy);
1003
1004 if (!empty($hidden_terms) && in_array($item->term_id, $hidden_terms)) {
1005 return esc_html__('Hidden', 'simple-tags');
1006 }
1007
1008 return esc_html__('Live', 'simple-tags');
1009 }
1010
1011 /**
1012 * Render a column when no column specific method exist.
1013 *
1014 * @param array $item
1015 * @param string $column_name
1016 *
1017 * @return mixed
1018 */
1019 public function column_default($item, $column_name)
1020 {
1021 return !empty($item->$column_name) ? $item->$column_name : '&mdash;';
1022 }
1023
1024 /** Text displayed when no stterm data is available */
1025 public function no_items()
1026 {
1027 esc_html_e('No terms found.', 'simple-tags');
1028 }
1029
1030 /**
1031 * Displays the search box.
1032 *
1033 * @param string $text The 'submit' button label.
1034 * @param string $input_id ID attribute value for the search input field.
1035 *
1036 *
1037 */
1038 public function search_box($text, $input_id)
1039 {
1040 // Hide search box if taxopress_show_all=1
1041 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
1042 if ((!empty($_REQUEST['taxopress_show_all']) && $_REQUEST['taxopress_show_all'] == '1')) {
1043 return;
1044 }
1045
1046 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
1047 if (empty($_REQUEST['s']) && !$this->has_items()) {
1048 //return;
1049 }
1050
1051 $input_id = $input_id . '-search-input';
1052
1053 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1054 if (!empty($_REQUEST['orderby'])) {
1055 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1056 echo '<input type="hidden" name="orderby" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['orderby']))) . '" />';
1057 }
1058 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1059 if (!empty($_REQUEST['order'])) {
1060 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1061 echo '<input type="hidden" name="order" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['order']))) . '" />';
1062 }
1063 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1064 if (!empty($_REQUEST['page'])) {
1065 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1066 echo '<input type="hidden" name="page" value="' . esc_attr(sanitize_text_field(wp_unslash($_REQUEST['page']))) . '" />';
1067 }
1068
1069 $custom_filters = ['terms_filter_post_type', 'terms_filter_taxonomy', 'taxonomy_type'];
1070
1071 foreach ($custom_filters as $custom_filter) {
1072 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
1073 $filter_value = !empty($_REQUEST[$custom_filter]) ? sanitize_text_field(wp_unslash($_REQUEST[$custom_filter])) : '';
1074 echo '<input type="hidden" name="' . esc_attr($custom_filter) . '" value="' . esc_attr($filter_value) . '" />';
1075 }
1076 ?>
1077 <p class="search-box">
1078 <label class="screen-reader-text" for="<?php echo esc_attr($input_id); ?>"><?php echo esc_html($text); ?>:</label>
1079 <input type="search" id="<?php echo esc_attr($input_id); ?>" name="s" value="<?php _admin_search_query(); ?>" />
1080 <?php submit_button($text, '', '', false, ['id' => 'taxopress-terms-search-submit']); ?>
1081 </p>
1082 <?php
1083 }
1084
1085 /**
1086 * Sets up the items (roles) to list.
1087 */
1088 public function prepare_items()
1089 {
1090
1091 $this->_column_headers = $this->get_column_info();
1092 $this->process_bulk_action();
1093
1094 /**
1095 * First, lets decide how many records per page to show
1096 */
1097 $per_page = $this->get_items_per_page('st_terms_per_page', 20);
1098
1099 /**
1100 * Fetch the data
1101 */
1102 $data = $this->get_st_Terms();
1103 if (!empty($data)) {
1104 update_termmeta_cache(wp_list_pluck($data, 'term_id'));
1105 }
1106
1107 /**
1108 * Pagination.
1109 */
1110 $current_page = $this->get_pagenum();
1111 $total_items = $this->record_count();
1112
1113 /**
1114 * Now we can add the data to the items property, where it can be used by the rest of the class.
1115 */
1116 $this->items = $data;
1117
1118 /**
1119 * We also have to register our pagination options & calculations.
1120 */
1121 $this->set_pagination_args([
1122 'total_items' => $total_items, //calculate the total number of items
1123 'per_page' => $per_page, //determine how many items to show on a page
1124 'total_pages' => ceil($total_items / $per_page) //calculate the total number of pages
1125 ]);
1126 }
1127
1128 /**
1129 * Generates and display row actions links for the list table.
1130 *
1131 * @param object $item The item being acted upon.
1132 * @param string $column_name Current column name.
1133 * @param string $primary Primary column name.
1134 *
1135 * @return string The row actions HTML, or an empty string if the current column is the primary column.
1136 */
1137 protected function handle_row_actions($item, $column_name, $primary)
1138 {
1139 $taxonomy = get_taxonomy($item->taxonomy);
1140
1141 //Build row actions
1142 $actions = [];
1143
1144 if (current_user_can('edit_term', $item->term_id)) {
1145 $actions['edit'] = sprintf(
1146 '<a href="%s">%s</a>',
1147 add_query_arg(
1148 [
1149 'taxonomy' => $item->taxonomy,
1150 'tag_ID' => $item->term_id,
1151 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
1152 ],
1153 admin_url('term.php')
1154 ),
1155 esc_html__('Edit', 'simple-tags')
1156 );
1157 }
1158
1159 // Only add other actions if not viewing via taxopress_terms_taxonomy
1160 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
1161 if (empty($_REQUEST['taxopress_terms_taxonomy'])) {
1162 if (current_user_can('edit_term', $item->term_id)) {
1163 $actions['inline hide-if-no-js'] = sprintf(
1164 '<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>',
1165 /* translators: %s: Taxonomy term name. */
1166 esc_attr(sprintf(esc_html__('Quick edit &#8220;%s&#8221; inline', 'simple-tags'), $item->name)),
1167 esc_html__('Quick&nbsp;Edit', 'simple-tags')
1168 );
1169 }
1170
1171 if (current_user_can('edit_term', $item->term_id)) {
1172 $actions['remove_posts'] = sprintf(
1173 '<a href="%s">%s</a>',
1174 add_query_arg(
1175 taxopress_get_terms_screen_query_args([
1176 'action' => 'taxopress-remove-from-posts',
1177 'taxopress_terms' => esc_attr($item->term_id),
1178 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1179 ]),
1180 admin_url('admin.php')
1181 ),
1182 esc_html__('Remove From All Posts', 'simple-tags')
1183 );
1184 }
1185
1186 if (current_user_can('delete_term', $item->term_id)) {
1187 $actions['delete'] = sprintf(
1188 '<a href="%s" class="delete-terms">%s</a>',
1189 add_query_arg(
1190 taxopress_get_terms_screen_query_args([
1191 'action' => 'taxopress-delete-terms',
1192 'taxopress_terms' => esc_attr($item->term_id),
1193 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1194 ]),
1195 admin_url('admin.php')
1196 ),
1197 esc_html__('Delete', 'simple-tags')
1198 );
1199 }
1200
1201 if (is_taxonomy_viewable($item->taxonomy)) {
1202 $actions['view'] = sprintf(
1203 '<a href="%s">%s</a>',
1204 get_term_link($item->term_id),
1205 esc_html__('View', 'simple-tags')
1206 );
1207 }
1208
1209 $actions['copy_term'] = sprintf(
1210 '<a href="%s">%s</a>',
1211 add_query_arg(
1212 taxopress_get_terms_screen_query_args([
1213 'action' => 'taxopress-copy-term',
1214 'taxopress_terms' => esc_attr($item->term_id),
1215 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
1216 ]),
1217 admin_url('admin.php')
1218 ),
1219 esc_html__('Copy', 'simple-tags')
1220 );
1221 $actions = apply_filters('taxopress_terms_row_actions', $actions, $item);
1222 }
1223 return $column_name === $primary ? $this->row_actions($actions, false) : '';
1224 }
1225
1226 /**
1227 * Method for synonyms column
1228 *
1229 * @param array $item
1230 *
1231 * @return string
1232 */
1233 protected function column_synonyms($item)
1234 {
1235 $term_synonyms = taxopress_get_term_synonyms($item->term_id);
1236 if (!empty($term_synonyms)) {
1237 return join(', ', $term_synonyms);
1238 } else {
1239 return '-';
1240 }
1241 }
1242
1243 /**
1244 * Method for linked_terms column
1245 *
1246 * @param array $item
1247 *
1248 * @return string
1249 */
1250 protected function column_linked_terms($item)
1251 {
1252 $term_linked_terms = taxopress_get_linked_terms($item->term_id);
1253 if (!empty($term_linked_terms)) {
1254 $term_linked_term_names = [];
1255 foreach ($term_linked_terms as $term_linked_term) {
1256 $linked_term_data = taxopress_get_linked_term_data($term_linked_term, $item->term_id);
1257 $term_linked_term_names[] = $linked_term_data->term_name . ' (' . $linked_term_data->term_taxonomy . ')';
1258 }
1259 return join(', ', $term_linked_term_names);
1260 } else {
1261 return '-';
1262 }
1263 }
1264
1265 /**
1266 * Method for name column
1267 *
1268 * @param array $item
1269 *
1270 * @return string
1271 */
1272 protected function column_name($item)
1273 {
1274 $taxonomy = get_taxonomy($item->taxonomy);
1275
1276 // Add dashes for hierarchy
1277 $depth = isset($item->taxopress_depth) ? (int)$item->taxopress_depth : 0;
1278 $dash = $depth > 0 ? str_repeat('&mdash; ', $depth) : '';
1279
1280 $title = sprintf(
1281 '<a href="%1$s"><strong><span class="row-title">%2$s%3$s</span></strong></a>',
1282 add_query_arg(
1283 [
1284 'taxonomy' => $item->taxonomy,
1285 'tag_ID' => $item->term_id,
1286 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
1287 ],
1288 admin_url('term.php')
1289 ),
1290 $dash,
1291 esc_html($item->name)
1292 );
1293
1294 $title .= ' <span class="taxopress-term-spinner" style="display:none;vertical-align:middle;"><span class="spinner is-active"></span></span>';
1295
1296 // Use the already-loaded term object and apply edit filters without an extra uncached DB query.
1297 $qe_data = sanitize_term(clone $item, $item->taxonomy, 'edit');
1298
1299 $title .= '<div class="hidden" id="inline_' . $qe_data->term_id . '">';
1300 $title .= '<div class="taxonomy">' . $item->taxonomy . '</div>';
1301 $title .= '<div class="name">' . $qe_data->name . '</div>';
1302
1303 $title .= '<div class="slug">' . apply_filters('editable_slug', $qe_data->slug, $qe_data) . '</div>';
1304 $title .= '<div class="parent">' . $qe_data->parent . '</div>
1305 </div>';
1306
1307 return $title;
1308 }
1309
1310 /**
1311 * The action column
1312 *
1313 * @param $item
1314 *
1315 * @return string
1316 */
1317 protected function column_slug($item)
1318 {
1319 return !empty($item->slug) ? $item->slug : '&mdash;';
1320 }
1321
1322 /**
1323 * The action column
1324 *
1325 * @param $item
1326 *
1327 * @return string
1328 */
1329 protected function column_posttypes($item)
1330 {
1331 $posttype = '';
1332 $sn = 0;
1333 $taxonomy = get_taxonomy($item->taxonomy);
1334 foreach ($taxonomy->object_type as $objecttype) {
1335 $sn++;
1336 $post_type_object = get_post_type_object($objecttype);
1337 if (is_object($post_type_object)) {
1338 $posttype .= $post_type_object->label;
1339 if ($sn < count($taxonomy->object_type)) {
1340 $posttype .= ', ';
1341 }
1342 }
1343 }
1344
1345 return $posttype;
1346 }
1347
1348 /**
1349 * The action column
1350 *
1351 * @param $item
1352 *
1353 * @return string
1354 */
1355 protected function column_count($item)
1356 {
1357 $term_counts = isset($item->count) ? (int) $item->count : 0;
1358
1359 return sprintf(
1360 '<a href="%s" class="">%s</a>',
1361 add_query_arg(
1362 [
1363 'page' => 'st_posts',
1364 'posts_term_filter' => (int) $item->term_id,
1365 ],
1366 admin_url('admin.php')
1367 ),
1368 number_format_i18n($term_counts)
1369 );
1370 }
1371
1372 /**
1373 * The action column
1374 *
1375 * @param $item
1376 *
1377 * @return string
1378 */
1379 protected function column_description($item)
1380 {
1381
1382 return term_description($item->term_id);
1383 }
1384
1385 /**
1386 * Method for taxonomy column
1387 *
1388 * @param array $item
1389 *
1390 * @return string
1391 */
1392 protected function column_taxonomy($item)
1393 {
1394 $taxonomy = get_taxonomy($item->taxonomy);
1395
1396 if ($taxonomy) {
1397 $return = sprintf(
1398 '<a href="%1$s">%2$s</a>',
1399 add_query_arg(
1400 [
1401 'page' => 'st_taxonomies',
1402 'add' => 'taxonomy',
1403 'action' => 'edit',
1404 'taxopress_taxonomy' => $taxonomy->name,
1405 ],
1406 taxopress_admin_url('admin.php')
1407 ),
1408 esc_html($taxonomy->labels->name)
1409 );
1410 } else {
1411 $return = '&mdash;';
1412 }
1413
1414 return $return;
1415 }
1416
1417 /**
1418 * Outputs the hidden row displayed when inline editing
1419 *
1420 * @since 3.1.0
1421 */
1422 public function inline_edit()
1423 {
1424 ?>
1425
1426 <form method="get">
1427 <table style="display: none">
1428 <tbody id="inlineedit">
1429
1430 <tr id="inline-edit" class="inline-edit-row" style="display: none">
1431 <td colspan="<?php echo esc_attr($this->get_column_count()); ?>" class="colspanchange">
1432
1433 <fieldset>
1434 <legend class="inline-edit-legend"><?php esc_html_e('Quick Edit', 'simple-tags'); ?></legend>
1435 <div class="inline-edit-col">
1436 <label>
1437 <span class="title"><?php _ex('Name', 'term name', 'simple-tags'); ?></span>
1438 <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span>
1439 </label>
1440
1441 <label>
1442 <span class="title"><?php esc_html_e('Slug', 'simple-tags'); ?></span>
1443 <span class="input-text-wrap"><input type="text" name="slug" class="ptitle" value="" /></span>
1444 </label>
1445 <label>
1446 <span class="taxonomy"><?php _ex('Taxonomy', 'term name', 'simple-tags'); ?></span>
1447
1448 <?php $taxonomies = get_all_taxopress_taxonomies(); ?>
1449 <select class="input-text-wrap edit-tax edit_taxonomy" name="edit_taxonomy">
1450 <?php
1451 foreach ($taxonomies as $taxonomy) {
1452 echo '<option value="' . esc_attr($taxonomy->name) . '">' . esc_html($taxonomy->labels->name) . '</option>';
1453 }
1454 ?>
1455 </select>
1456 </label>
1457 </div>
1458 </fieldset>
1459
1460 <div class="inline-edit-save submit">
1461 <button type="button" class="cancel button alignleft"><?php esc_html_e('Cancel', 'simple-tags'); ?></button>
1462 <button type="button" class="taxopress-save button button-primary alignright"><?php esc_html_e('Update', 'simple-tags'); ?></button>
1463 <span class="spinner"></span>
1464
1465 <?php wp_nonce_field('taxinlineeditnonce', '_inline_edit', false); ?>
1466 <br class="clear" />
1467
1468 <div class="notice notice-error notice-alt inline hidden taxopress-notice">
1469 <p class="error"></p>
1470 </div>
1471 </div>
1472
1473 </td>
1474 </tr>
1475
1476 </tbody>
1477 </table>
1478 </form>
1479 <?php
1480 }
1481 }
1482