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

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

1,117 lines 44.1 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 * Flatten a hierarchical terms array into a flat array.
22 *
23 * @param array $terms Array of term objects.
24 * @param int $max_depth Maximum depth to traverse (default 10).
25 * @return array Flattened array of term objects.
26 */
27 public function taxopress_flatten_terms_tree($terms, $max_depth = 7)
28 {
29 $flat = [];
30 $map = [];
31 $children_map = [];
32 $visited = [];
33
34 // Build a map of terms by ID and their children
35 foreach ($terms as $term) {
36 $map[$term->term_id] = $term;
37 $children_map[$term->parent][] = $term;
38 }
39
40 // Initialize stack with root-level terms (no valid parent)
41 $stack = [];
42 foreach ($terms as $term) {
43 if ($term->parent === 0 || !isset($map[$term->parent])) {
44 $stack[] = ['term' => $term, 'depth' => 0];
45 }
46 }
47
48 // Iterative depth-first traversal
49 while (!empty($stack)) {
50 $node = array_pop($stack);
51 $term = $node['term'];
52 $depth = $node['depth'];
53
54 if ($depth > $max_depth) {
55 continue;
56 }
57 if (isset($visited[$term->term_id])) {
58 continue; // Prevent cycles
59 }
60 $visited[$term->term_id] = true;
61
62 $flat[] = $term;
63
64 // Use pre-indexed children map
65 if (!empty($children_map[$term->term_id])) {
66 foreach (array_reverse($children_map[$term->term_id]) as $child) {
67 $stack[] = ['term' => $child, 'depth' => $depth + 1];
68 }
69 }
70 }
71
72 return $flat;
73 }
74
75 /**
76 * Arrange terms in hierarchical order with depth info for dash prefixing.
77 */
78 public function taxopress_arrange_terms_hierarchically($terms)
79 {
80 $terms_by_id = [];
81 $children = [];
82 foreach ($terms as $term) {
83 $terms_by_id[$term->term_id] = $term;
84 $children[$term->parent][] = $term->term_id;
85 }
86
87 $ordered = [];
88 $add_term = function ($term_id, $depth) use (&$add_term, &$terms_by_id, &$children, &$ordered) {
89 $term = $terms_by_id[$term_id];
90 $term->taxopress_depth = $depth;
91 $ordered[] = $term;
92 if (!empty($children[$term_id])) {
93 foreach ($children[$term_id] as $child_id) {
94 $add_term($child_id, $depth + 1);
95 }
96 }
97 };
98
99 // Start with root terms
100 foreach ($terms as $term) {
101 if ($term->parent == 0 || !isset($terms_by_id[$term->parent])) {
102 $add_term($term->term_id, 0);
103 }
104 }
105 return $ordered;
106 }
107
108 public function get_all_terms($count = false)
109 {
110
111 $taxonomies = array_keys(get_all_taxopress_taxonomies_request());
112 $taxonomy_settings = taxopress_get_all_edited_taxonomy_data();
113
114 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for search filtering
115 $search = (!empty($_REQUEST['s'])) ? sanitize_text_field($_REQUEST['s']) : '';
116
117 $items_per_page = $this->get_items_per_page('st_terms_per_page', 20);
118 $page = $this->get_pagenum();
119 $offset = ($page - 1) * $items_per_page;
120
121 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering and display
122 $selected_post_type = (!empty($_REQUEST['terms_filter_post_type'])) ? [sanitize_text_field($_REQUEST['terms_filter_post_type'])] : '';
123 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering and display
124 $selected_taxonomy = (!empty($_REQUEST['terms_filter_taxonomy'])) ? sanitize_text_field($_REQUEST['terms_filter_taxonomy']) : '';
125
126 $allowed_orderby = ['name', 'slug', 'taxonomy', 'count', 'id'];
127 $allowed_order = ['asc', 'desc'];
128
129 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for ordering
130 $requested_orderby = !empty($_REQUEST['orderby']) ? sanitize_key($_REQUEST['orderby']) : '';
131 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for ordering
132 $requested_order = !empty($_REQUEST['order']) ? strtolower(sanitize_text_field($_REQUEST['order'])) : '';
133
134 if (!in_array($requested_orderby, $allowed_orderby, true)) {
135 $requested_orderby = '';
136 }
137 if (!in_array($requested_order, $allowed_order, true)) {
138 $requested_order = '';
139 }
140
141 $order_setting = isset($taxonomy_settings[$selected_taxonomy]['order']) ? strtolower($taxonomy_settings[$selected_taxonomy]['order']) : 'desc';
142 $orderby_setting = isset($taxonomy_settings[$selected_taxonomy]['orderby']) ? $taxonomy_settings[$selected_taxonomy]['orderby'] : 'ID';
143
144 $orderby_setting = strtolower($orderby_setting);
145 if ($orderby_setting === 'id' || $orderby_setting === 'term_id') {
146 $orderby_setting = 'id';
147 }
148
149 if ($requested_orderby) {
150 $orderby_setting = $requested_orderby;
151 }
152 if ($requested_order) {
153 $order_setting = $requested_order;
154 }
155
156 // If viewing via taxopress_terms_taxonomy, override to show all terms in that taxonomy
157 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
158 if (!empty($_REQUEST['taxopress_terms_taxonomy'])) {
159 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
160 $selected_taxonomy = sanitize_text_field($_REQUEST['taxopress_terms_taxonomy']);
161 $taxonomies = [$selected_taxonomy];
162 $show_all_terms_in_taxonomy = false;
163 } else {
164 $show_all_terms_in_taxonomy = false;
165 if (!empty($selected_taxonomy)) {
166 $taxonomies = [$selected_taxonomy];
167 }
168 }
169
170 // Check if any taxonomy uses manual ordering
171 $manual_order = false;
172 foreach ($taxonomies as $taxonomy) {
173 $order_setting = isset($taxonomy_settings[$taxonomy]['order']) ? $taxonomy_settings[$taxonomy]['order'] : 'desc';
174 $orderby_setting = isset($taxonomy_settings[$selected_taxonomy]['orderby']) ? $taxonomy_settings[$selected_taxonomy]['orderby'] : 'ID';
175 if ($requested_orderby) {
176 $orderby_setting = $requested_orderby;
177 }
178 if ($requested_order) {
179 $order_setting = $requested_order;
180 }
181 if ($order_setting === 'taxopress_term_order') {
182 $manual_order = true;
183 break;
184 }
185 }
186
187 // If any taxonomy uses manual order, use the original per-taxonomy logic
188 if ($manual_order) {
189 $terms = [];
190 foreach ($taxonomies as $taxonomy) {
191 $custom_order = get_option('taxopress_term_order_' . $taxonomy, []);
192 $order_setting = isset($taxonomy_settings[$taxonomy]['order']) ? $taxonomy_settings[$taxonomy]['order'] : 'desc';
193 $orderby_setting = isset($taxonomy_settings[$taxonomy]['orderby']) ? $taxonomy_settings[$taxonomy]['orderby'] : 'ID';
194 $use_custom_order = ($order_setting === 'taxopress_term_order');
195
196 $terms_attr = [
197 'taxonomy' => [$taxonomy],
198 'post_types' => $selected_post_type,
199 'hide_empty' => false,
200 'pad_counts' => true,
201 'update_term_meta_cache' => true,
202 'search' => $search,
203 ];
204
205 if (!$use_custom_order) {
206 $terms_attr['orderby'] = $orderby_setting;
207 $terms_attr['order'] = $order_setting;
208 }
209
210 // Only paginate after merging all terms
211 $taxonomy_terms = get_terms($terms_attr);
212
213 if (empty($taxonomy_terms) || is_wp_error($taxonomy_terms)) {
214 continue;
215 }
216
217 if ($use_custom_order) {
218 // Manual custom ordering
219 $terms_by_id = [];
220 $new_terms = [];
221 $ordered_terms = [];
222
223 foreach ($taxonomy_terms as $term) {
224 $terms_by_id[$term->term_id] = $term;
225 }
226
227 // Terms not in custom order
228 foreach ($terms_by_id as $term_id => $term) {
229 if (!in_array($term_id, $custom_order)) {
230 $new_terms[] = $term;
231 }
232 }
233
234 // Ordered terms
235 foreach ($custom_order as $term_id) {
236 if (isset($terms_by_id[$term_id])) {
237 $ordered_terms[] = $terms_by_id[$term_id];
238 }
239 }
240
241 // Merge: new (unordered) terms first, then custom-ordered ones
242 $terms = array_merge($terms, $new_terms, $ordered_terms);
243 } else {
244 if ($orderby_setting === 'random') {
245 shuffle($taxonomy_terms);
246 if ($order_setting === 'desc') {
247 $taxonomy_terms = array_reverse($taxonomy_terms);
248 }
249 }
250 $terms = array_merge($terms, $taxonomy_terms);
251 }
252 }
253
254 // Paginate after merging, unless showing all terms in taxonomy
255 if (!$count) {
256 $terms = array_slice($terms, $offset, $items_per_page);
257 }
258
259 // HIERARCHY SUPPORT
260 if (!empty($selected_taxonomy)) {
261 $terms = $this->taxopress_arrange_terms_hierarchically($terms);
262 } else {
263 $terms = $this->taxopress_flatten_terms_tree($terms);
264 }
265
266 return $terms;
267 }
268
269 // If no manual ordering, use the efficient all-in-one get_terms
270 $terms_attr = [
271 'taxonomy' => $taxonomies,
272 'post_types' => $selected_post_type,
273 'orderby' => $orderby_setting,
274 'order' => $order_setting,
275 'search' => $search,
276 'hide_empty' => false,
277 'pad_counts' => true,
278 'update_term_meta_cache' => true,
279 ];
280 if ($count || $show_all_terms_in_taxonomy) {
281 $terms_attr['number'] = 0;
282 } else {
283 $terms_attr['offset'] = $offset;
284 $terms_attr['number'] = $items_per_page;
285 }
286
287 $terms = get_terms($terms_attr);
288
289 if (empty($terms) || is_wp_error($terms)) {
290 return [];
291 }
292
293 if (!empty($requested_orderby) && $requested_orderby === 'taxonomy') {
294 usort($terms, function ($a, $b) use ($order_setting) {
295 $cmp = strcmp($a->taxonomy, $b->taxonomy);
296 return ($order_setting === 'desc') ? -$cmp : $cmp;
297 });
298 }
299
300 if ($orderby_setting === 'random') {
301 shuffle($terms);
302 if ($order_setting === 'desc') {
303 $terms = array_reverse($terms);
304 }
305 }
306
307 // HIERARCHY SUPPORT
308 if (!empty($selected_taxonomy)) {
309 $terms = $this->taxopress_arrange_terms_hierarchically($terms);
310 } else {
311 $terms = $this->taxopress_flatten_terms_tree($terms);
312 }
313
314 return $terms;
315 }
316
317 /**
318 * Retrieve st_Terms data from the database
319 *
320 * @param int $per_page
321 * @param int $page_number
322 *
323 * @return mixed
324 */
325 public function get_st_Terms()
326 {
327 return $this->get_all_terms();
328 }
329
330 /**
331 * Returns the count of records in the database.
332 *
333 * @return null|string
334 */
335 public function record_count()
336 {
337 return count($this->get_all_terms(true));
338 }
339
340 /**
341 * Show single row item
342 *
343 * @param array $item
344 */
345 public function single_row($item)
346 {
347 $class = ['st-terms-tr'];
348 $id = 'term-' . $item->term_id . '';
349 echo sprintf('<tr id="%s" class="%s">', esc_attr($id), esc_attr(implode(' ', $class)));
350 $this->single_row_columns($item);
351 echo '</tr>';
352 }
353
354 /**
355 * Associative array of columns
356 *
357 * @return array
358 */
359 public function get_columns()
360 {
361
362 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
363 if (!empty($_REQUEST['taxopress_terms_taxonomy'])) {
364 return [
365 'name' => esc_html__('Title', 'simple-tags'),
366 'description' => esc_html__('Description', 'simple-tags'),
367 'count' => esc_html__('Count', 'simple-tags'),
368 ];
369 }
370 $columns = [
371 'cb' => '<input type="checkbox" />',
372 'name' => esc_html__('Title', 'simple-tags'),
373 'slug' => esc_html__('Slug', 'simple-tags'),
374 'description' => esc_html__('Description', 'simple-tags'),
375 'taxonomy' => esc_html__('Taxonomy', 'simple-tags'),
376 'posttypes' => esc_html__('Post Types', 'simple-tags'),
377 'taxopress_custom_url' => esc_html__('Custom URL', 'simple-tags'),
378 'synonyms' => esc_html__('Synonyms', 'simple-tags'),
379 'linked_terms' => esc_html__('Linked Terms', 'simple-tags'),
380 'hidden_status' => esc_html__('Status', 'simple-tags'),
381 'count' => esc_html__('Count', 'simple-tags')
382 ];
383
384 if (!taxopress_is_pro_version()) {
385 unset($columns['synonyms']);
386 unset($columns['linked_terms']);
387 }
388
389 if (!(int) SimpleTags_Plugin::get_option_value('enable_hidden_terms')) {
390 unset($columns['hidden_status']);
391 }
392
393 return $columns;
394 }
395
396 /**
397 * Columns to make sortable.
398 *
399 * @return array
400 */
401 protected function get_sortable_columns()
402 {
403 $sortable_columns = [
404 'name' => ['name', true],
405 'slug' => ['slug', true],
406 'taxonomy' => ['taxonomy', true],
407 'count' => ['count', true],
408 ];
409
410 return $sortable_columns;
411 }
412
413 /**
414 * Render the bulk edit checkbox
415 *
416 * @param array $item
417 *
418 * @return string
419 */
420 public function column_cb($item)
421 {
422 return sprintf('<input type="checkbox" name="%1$s[]" value="%2$s" />', 'taxopress_terms', $item->term_id);
423 }
424
425 /**
426 * Get the bulk actions to show in the top page dropdown
427 *
428 * @return array
429 */
430 protected function get_bulk_actions()
431 {
432 $actions = [
433 'taxopress-terms-delete-terms' => esc_html__('Delete', 'simple-tags'),
434 'taxopress-terms-copy-terms' => esc_html__('Copy', 'simple-tags')
435 ];
436
437 return $actions;
438 }
439
440 /**
441 * Add custom filter to tablenav
442 *
443 * @param string $which
444 */
445 protected function extra_tablenav($which)
446 {
447 // Hide filters if taxopress_show_all=1
448 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
449 if ('top' === $which && empty($_REQUEST['taxopress_show_all'])) {
450 $post_types = get_post_types(['public' => true], 'objects');
451
452 $taxonomies = get_all_taxopress_taxonomies_request();
453
454 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
455 $selected_post_type = (!empty($_REQUEST['terms_filter_post_type'])) ? sanitize_text_field($_REQUEST['terms_filter_post_type']) : '';
456 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
457 $selected_taxonomy = (!empty($_REQUEST['terms_filter_taxonomy'])) ? sanitize_text_field($_REQUEST['terms_filter_taxonomy']) : '';
458 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
459 $selected_post = (!empty($_REQUEST['destination_post'])) ? sanitize_text_field($_REQUEST['destination_post']) : '';
460
461 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying GET parameter for taxonomy type
462 $selected_option = 'public';
463 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
464 if (isset($_GET['taxonomy_type']) && $_GET['taxonomy_type'] === 'all') {
465 $selected_option = 'all';
466 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
467 } elseif (isset($_GET['taxonomy_type']) && $_GET['taxonomy_type'] === 'private') {
468 $selected_option = 'private';
469 }
470 ?>
471
472 <div class="alignleft actions autoterms-terms-table-copy" id="taxopress-copy-selection-boxes" style="display: none;">
473 <select class="auto-terms-terms-copy-select" name="taxopress_destination_taxonomy" id="terms_copy_select_destination_taxonomy">
474 <option value=""><?php esc_html_e('Select Destination Taxonomy', 'simple-tags'); ?></option> <?php
475 foreach ($taxonomies as $taxonomy) {
476 echo '<option value="' . esc_attr($taxonomy->name) . '">' . esc_html($taxonomy->labels->name) . '</option>';
477 } ?>
478 </select>
479
480 <select class="auto-terms-terms-copy-select" name="taxopress_destination_post_type" id="terms_copy_select_destination_post">
481 <?php
482 $post_type_label = !empty($selected_post_type) ? esc_html($selected_post_type) : esc_html__('post type', 'simple-tags');
483 ?>
484 <option value=""><?php printf(esc_html__('Select Destination %s', 'simple-tags'), esc_html($post_type_label)); ?></option>
485 <option value="all" <?php selected($selected_post, 'all'); ?>><?php printf(esc_html__('All %s', 'simple-tags'), esc_html($post_type_label)); ?></option>.
486 <?php
487 // I want to show all posts when no post type is selected
488 if (empty($selected_post_type)) {
489 $all_posts = get_posts(['post_type' => 'any', 'numberposts' => -1]);
490 foreach ($all_posts as $post) : ?>
491 <option value="<?php echo esc_attr($post->ID); ?>" <?php selected($selected_post, $post->ID); ?>>
492 <?php echo esc_html($post->post_title); ?>
493 </option>
494 <?php endforeach;
495 } else {
496 // Show posts only for selected post type
497 $posts = get_posts(['post_type' => $selected_post_type, 'numberposts' => -1]);
498 foreach ($posts as $post) : ?>
499 <option value="<?php echo esc_attr($post->ID); ?>" <?php selected($selected_post, $post->ID); ?>>
500 <?php echo esc_html($post->post_title); ?>
501 </option>
502 <?php endforeach;
503 }
504 ?>
505 </select>
506 </div>
507 <div class="alignleft actions autoterms-terms-table-filter">
508
509 <select class="auto-terms-terms-filter-select" name="terms_filter_select_post_type" id="terms_filter_select_post_type">
510 <option value=""><?php esc_html_e('Post type', 'simple-tags'); ?></option>
511 <?php
512 foreach ($post_types as $post_type) {
513 echo '<option value="' . esc_attr($post_type->name) . '" ' . selected($selected_post_type, $post_type->name, false) . '>' . esc_html($post_type->label) . '</option>';
514 }
515 ?>
516 </select>
517
518 <select class="auto-terms-terms-filter-select" name="terms_filter_select_taxonomy" id="terms_filter_select_taxonomy">
519 <option value=""><?php esc_html_e('Taxonomy', 'simple-tags'); ?></option>
520 <?php
521 foreach ($taxonomies as $taxonomy) {
522 echo '<option value="' . esc_attr($taxonomy->name) . '" ' . selected($selected_taxonomy, $taxonomy->name, false) . '>' . esc_html($taxonomy->labels->name) . '</option>';
523 }
524 ?>
525 </select>
526
527 <select class="auto-terms-terms-filter-select" name="terms_filter_select_taxonomy_type" id="terms_filter_select_taxonomy_type">
528 <option value="all" <?php echo($selected_option === 'all' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('All Taxonomies', 'simple-tags'); ?></option>
529 <option value="public" <?php echo($selected_option === 'public' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('Public Taxonomies', 'simple-tags'); ?></option>
530 <option value="private" <?php echo($selected_option === 'private' ? 'selected="selected"' : ''); ?>><?php echo esc_html__('Private Taxonomies', 'simple-tags'); ?></option>
531 </select>
532
533 <a href="javascript:void(0)" class="taxopress-terms-tablenav-filter button"><?php esc_html_e('Filter', 'simple-tags'); ?></a>
534
535 </div>
536 <?php
537 }
538 }
539
540 /**
541 * Process bulk actions
542 */
543 public function process_bulk_action()
544 {
545
546 $query_arg = '_wpnonce';
547 $action = 'bulk-' . $this->_args['plural'];
548 $checked = isset($_REQUEST[$query_arg]) ? wp_verify_nonce(sanitize_key($_REQUEST[$query_arg]), $action) : false;
549
550 if (!$checked || !current_user_can('simple_tags')) {
551 return;
552 }
553
554 if ($this->current_action() === 'taxopress-terms-delete-terms') {
555 $taxopress_terms = !empty($_REQUEST['taxopress_terms']) ? array_map('sanitize_text_field', (array)$_REQUEST['taxopress_terms']) : [];
556 if (!empty($taxopress_terms)) {
557 foreach ($taxopress_terms as $taxopress_term) {
558 $term = get_term($taxopress_term);
559 wp_delete_term($term->term_id, $term->taxonomy);
560 }
561 if (count($taxopress_terms) > 1) {
562 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
563 echo taxopress_admin_notices_helper(esc_html__('Terms deleted successfully.', 'simple-tags'), false);
564 } else {
565 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
566 echo taxopress_admin_notices_helper(esc_html__('Term deleted successfully.', 'simple-tags'), false);
567 }
568 }
569 }
570 if ($this->current_action() === 'taxopress-terms-copy-terms') {
571 $taxopress_terms = !empty($_REQUEST['taxopress_terms']) ? array_map('sanitize_text_field', (array)$_REQUEST['taxopress_terms']) : [];
572 $destination_taxonomy = !empty($_REQUEST['taxopress_destination_taxonomy']) ? sanitize_text_field($_REQUEST['taxopress_destination_taxonomy']) : '';
573 $destination_post = !empty($_REQUEST['taxopress_destination_post_type']) ? sanitize_text_field($_REQUEST['taxopress_destination_post_type']) : '';
574
575 if (!empty($taxopress_terms) && !empty($destination_taxonomy)) {
576 foreach ($taxopress_terms as $taxopress_term) {
577 $term = get_term($taxopress_term);
578 wp_insert_term($term->name, $destination_taxonomy, [
579 'slug' => $term->slug,
580 'description' => $term->description,
581 ]);
582 if (!empty($destination_post) && $destination_post !== 'all') {
583 wp_set_object_terms($destination_post, [$term->term_id], $destination_taxonomy, true);
584 }
585
586 if ($destination_post === 'all') {
587 $all_posts = get_posts(['post_type' => 'any', 'numberposts' => -1]);
588 foreach ($all_posts as $post) {
589 wp_set_object_terms($post->ID, [$term->term_id], $destination_taxonomy, true);
590 }
591 }
592 }
593 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
594 echo taxopress_admin_notices_helper(esc_html__('Term(s) copied successfully.', 'simple-tags'), true);
595 }
596 }
597 }
598
599 protected function column_taxopress_custom_url($item)
600 {
601 $taxopress_custom_url = get_term_meta($item->term_id, 'taxopress_custom_url', true);
602 return (!empty($taxopress_custom_url) && filter_var($taxopress_custom_url, FILTER_VALIDATE_URL))
603 ? sprintf('<a href="%s" target="_blank">%s</a>', esc_url($taxopress_custom_url), esc_html($taxopress_custom_url))
604 : '-';
605 }
606
607 protected function column_hidden_status($item)
608 {
609 $hidden_terms = get_transient('taxopress_hidden_terms_' . $item->taxonomy);
610
611 if (!empty($hidden_terms) && in_array($item->term_id, $hidden_terms)) {
612 return esc_html__('Hidden', 'simple-tags');
613 }
614
615 return esc_html__('Live', 'simple-tags');
616 }
617
618 /**
619 * Render a column when no column specific method exist.
620 *
621 * @param array $item
622 * @param string $column_name
623 *
624 * @return mixed
625 */
626 public function column_default($item, $column_name)
627 {
628 return !empty($item->$column_name) ? $item->$column_name : '&mdash;';
629 }
630
631 /** Text displayed when no stterm data is available */
632 public function no_items()
633 {
634 esc_html_e('No terms found.', 'simple-tags');
635 }
636
637 /**
638 * Displays the search box.
639 *
640 * @param string $text The 'submit' button label.
641 * @param string $input_id ID attribute value for the search input field.
642 *
643 *
644 */
645 public function search_box($text, $input_id)
646 {
647 // Hide search box if taxopress_show_all=1
648 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
649 if ((!empty($_REQUEST['taxopress_show_all']) && $_REQUEST['taxopress_show_all'] == '1')) {
650 return;
651 }
652
653 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for display filtering
654 if (empty($_REQUEST['s']) && !$this->has_items()) {
655 //return;
656 }
657
658 $input_id = $input_id . '-search-input';
659
660 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
661 if (!empty($_REQUEST['orderby'])) {
662 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
663 echo '<input type="hidden" name="orderby" value="' . esc_attr(sanitize_text_field($_REQUEST['orderby'])) . '" />';
664 }
665 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
666 if (!empty($_REQUEST['order'])) {
667 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
668 echo '<input type="hidden" name="order" value="' . esc_attr(sanitize_text_field($_REQUEST['order'])) . '" />';
669 }
670 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
671 if (!empty($_REQUEST['page'])) {
672 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
673 echo '<input type="hidden" name="page" value="' . esc_attr(sanitize_text_field($_REQUEST['page'])) . '" />';
674 }
675
676 $custom_filters = ['terms_filter_post_type', 'terms_filter_taxonomy', 'taxonomy_type'];
677
678 foreach ($custom_filters as $custom_filter) {
679 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameters for filtering
680 $filter_value = !empty($_REQUEST[$custom_filter]) ? sanitize_text_field($_REQUEST[$custom_filter]) : '';
681 echo '<input type="hidden" name="' . esc_attr($custom_filter) . '" value="' . esc_attr($filter_value) . '" />';
682 }
683 ?>
684 <p class="search-box">
685 <label class="screen-reader-text" for="<?php echo esc_attr($input_id); ?>"><?php echo esc_html($text); ?>:</label>
686 <input type="search" id="<?php echo esc_attr($input_id); ?>" name="s" value="<?php _admin_search_query(); ?>" />
687 <?php submit_button($text, '', '', false, ['id' => 'taxopress-terms-search-submit']); ?>
688 </p>
689 <?php
690 }
691
692 /**
693 * Sets up the items (roles) to list.
694 */
695 public function prepare_items()
696 {
697
698 $this->_column_headers = $this->get_column_info();
699 $this->process_bulk_action();
700
701 /**
702 * First, lets decide how many records per page to show
703 */
704 $per_page = $this->get_items_per_page('st_terms_per_page', 20);
705
706 /**
707 * Fetch the data
708 */
709 $data = $this->get_st_Terms();
710
711 /**
712 * Pagination.
713 */
714 $current_page = $this->get_pagenum();
715 $total_items = $this->record_count();
716
717 /**
718 * Now we can add the data to the items property, where it can be used by the rest of the class.
719 */
720 $this->items = $data;
721
722 /**
723 * We also have to register our pagination options & calculations.
724 */
725 $this->set_pagination_args([
726 'total_items' => $total_items, //calculate the total number of items
727 'per_page' => $per_page, //determine how many items to show on a page
728 'total_pages' => ceil($total_items / $per_page) //calculate the total number of pages
729 ]);
730 }
731
732 /**
733 * Generates and display row actions links for the list table.
734 *
735 * @param object $item The item being acted upon.
736 * @param string $column_name Current column name.
737 * @param string $primary Primary column name.
738 *
739 * @return string The row actions HTML, or an empty string if the current column is the primary column.
740 */
741 protected function handle_row_actions($item, $column_name, $primary)
742 {
743 $taxonomy = get_taxonomy($item->taxonomy);
744
745 //Build row actions
746 $actions = [];
747
748 if (current_user_can('edit_term', $item->term_id)) {
749 $actions['edit'] = sprintf(
750 '<a href="%s">%s</a>',
751 add_query_arg(
752 [
753 'taxonomy' => $item->taxonomy,
754 'tag_ID' => $item->term_id,
755 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
756 ],
757 admin_url('term.php')
758 ),
759 esc_html__('Edit', 'simple-tags')
760 );
761 }
762
763 // Only add other actions if not viewing via taxopress_terms_taxonomy
764 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading non-state-modifying REQUEST parameter for taxonomy filtering
765 if (empty($_REQUEST['taxopress_terms_taxonomy'])) {
766 if (current_user_can('edit_term', $item->term_id)) {
767 $actions['inline hide-if-no-js'] = sprintf(
768 '<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>',
769 /* translators: %s: Taxonomy term name. */
770 esc_attr(sprintf(esc_html__('Quick edit &#8220;%s&#8221; inline', 'simple-tags'), $item->name)),
771 esc_html__('Quick&nbsp;Edit', 'simple-tags')
772 );
773 }
774
775 if (current_user_can('edit_term', $item->term_id)) {
776 $actions['remove_posts'] = sprintf(
777 '<a href="%s">%s</a>',
778 add_query_arg(
779 [
780 'page' => 'st_terms',
781 'action' => 'taxopress-remove-from-posts',
782 'taxopress_terms' => esc_attr($item->term_id),
783 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
784 ],
785 admin_url('admin.php')
786 ),
787 esc_html__('Remove From All Posts', 'simple-tags')
788 );
789 }
790
791 if (current_user_can('delete_term', $item->term_id)) {
792 $actions['delete'] = sprintf(
793 '<a href="%s" class="delete-terms">%s</a>',
794 add_query_arg(
795 [
796 'page' => 'st_terms',
797 'action' => 'taxopress-delete-terms',
798 'taxopress_terms' => esc_attr($item->term_id),
799 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
800 ],
801 admin_url('admin.php')
802 ),
803 esc_html__('Delete', 'simple-tags')
804 );
805 }
806
807 if (is_taxonomy_viewable($item->taxonomy)) {
808 $actions['view'] = sprintf(
809 '<a href="%s">%s</a>',
810 get_term_link($item->term_id),
811 esc_html__('View', 'simple-tags')
812 );
813 }
814
815 $actions['copy_term'] = sprintf(
816 '<a href="%s">%s</a>',
817 add_query_arg(
818 [
819 'page' => 'st_terms',
820 'action' => 'taxopress-copy-term',
821 'taxopress_terms' => esc_attr($item->term_id),
822 '_wpnonce' => wp_create_nonce('terms-action-request-nonce')
823 ],
824 admin_url('admin.php')
825 ),
826 esc_html__('Copy', 'simple-tags')
827 );
828 $actions = apply_filters('taxopress_terms_row_actions', $actions, $item);
829 }
830 return $column_name === $primary ? $this->row_actions($actions, false) : '';
831 }
832
833 /**
834 * Method for synonyms column
835 *
836 * @param array $item
837 *
838 * @return string
839 */
840 protected function column_synonyms($item)
841 {
842 $term_synonyms = taxopress_get_term_synonyms($item->term_id);
843 if (!empty($term_synonyms)) {
844 return join(', ', $term_synonyms);
845 } else {
846 return '-';
847 }
848 }
849
850 /**
851 * Method for linked_terms column
852 *
853 * @param array $item
854 *
855 * @return string
856 */
857 protected function column_linked_terms($item)
858 {
859 $term_linked_terms = taxopress_get_linked_terms($item->term_id);
860 if (!empty($term_linked_terms)) {
861 $term_linked_term_names = [];
862 foreach ($term_linked_terms as $term_linked_term) {
863 $linked_term_data = taxopress_get_linked_term_data($term_linked_term, $item->term_id);
864 $term_linked_term_names[] = $linked_term_data->term_name . ' (' . $linked_term_data->term_taxonomy . ')';
865 }
866 return join(', ', $term_linked_term_names);
867 } else {
868 return '-';
869 }
870 }
871
872 /**
873 * Method for name column
874 *
875 * @param array $item
876 *
877 * @return string
878 */
879 protected function column_name($item)
880 {
881 $taxonomy = get_taxonomy($item->taxonomy);
882
883 // Add dashes for hierarchy
884 $depth = isset($item->taxopress_depth) ? (int)$item->taxopress_depth : 0;
885 $dash = $depth > 0 ? str_repeat('&mdash; ', $depth) : '';
886
887 $title = sprintf(
888 '<a href="%1$s"><strong><span class="row-title">%2$s%3$s</span></strong></a>',
889 add_query_arg(
890 [
891 'taxonomy' => $item->taxonomy,
892 'tag_ID' => $item->term_id,
893 'post_type' => isset($taxonomy->object_type[0]) ? $taxonomy->object_type[0] : 'post',
894 ],
895 admin_url('term.php')
896 ),
897 $dash,
898 esc_html($item->name)
899 );
900
901 $title .= ' <span class="taxopress-term-spinner" style="display:none;vertical-align:middle;"><span class="spinner is-active"></span></span>';
902
903 //for inline edit
904 $qe_data = get_term($item->term_id, $item->taxonomy, OBJECT, 'edit');
905
906 $title .= '<div class="hidden" id="inline_' . $qe_data->term_id . '">';
907 $title .= '<div class="taxonomy">' . $item->taxonomy . '</div>';
908 $title .= '<div class="name">' . $qe_data->name . '</div>';
909
910 $title .= '<div class="slug">' . apply_filters('editable_slug', $qe_data->slug, $qe_data) . '</div>';
911 $title .= '<div class="parent">' . $qe_data->parent . '</div>
912 </div>';
913
914 return $title;
915 }
916
917 /**
918 * The action column
919 *
920 * @param $item
921 *
922 * @return string
923 */
924 protected function column_slug($item)
925 {
926 return !empty($item->slug) ? $item->slug : '&mdash;';
927 }
928
929 /**
930 * The action column
931 *
932 * @param $item
933 *
934 * @return string
935 */
936 protected function column_posttypes($item)
937 {
938 $posttype = '';
939 $sn = 0;
940 $taxonomy = get_taxonomy($item->taxonomy);
941 foreach ($taxonomy->object_type as $objecttype) {
942 $sn++;
943 $post_type_object = get_post_type_object($objecttype);
944 if (is_object($post_type_object)) {
945 $posttype .= $post_type_object->label;
946 if ($sn < count($taxonomy->object_type)) {
947 $posttype .= ', ';
948 }
949 }
950 }
951
952 return $posttype;
953 }
954
955 /**
956 * The action column
957 *
958 * @param $item
959 *
960 * @return string
961 */
962 protected function column_count($item)
963 {
964 $term_counts = $this->count_posts_by_term($item->term_id, $item->taxonomy);
965
966 return sprintf(
967 '<a href="%s" class="">%s</a>',
968 add_query_arg(
969 [
970 'page' => 'st_posts',
971 'posts_term_filter' => (int) $item->term_id,
972 ],
973 admin_url('admin.php')
974 ),
975 number_format_i18n($term_counts)
976 );
977 }
978
979 protected function count_posts_by_term($term_id, $taxonomy)
980 {
981
982 $args = array(
983 'post_type' => array_keys(get_post_types(array('public' => true), 'names')),
984 'post_status' => 'any',
985 'posts_per_page' => 1,
986 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Necessary to filter posts by specific term for accurate count display
987 'tax_query' => array(
988 'relation' => 'AND',
989 array(
990 'taxonomy' => $taxonomy,
991 'field' => 'id',
992 'terms' => $term_id,
993 ),
994 ),
995 );
996
997 $term_count = new WP_Query($args);
998
999 if ($term_count->have_posts()) {
1000 return $term_count->found_posts;
1001 } else {
1002 return 0;
1003 }
1004 }
1005
1006
1007 /**
1008 * The action column
1009 *
1010 * @param $item
1011 *
1012 * @return string
1013 */
1014 protected function column_description($item)
1015 {
1016
1017 return term_description($item->term_id);
1018 }
1019
1020 /**
1021 * Method for taxonomy column
1022 *
1023 * @param array $item
1024 *
1025 * @return string
1026 */
1027 protected function column_taxonomy($item)
1028 {
1029 $taxonomy = get_taxonomy($item->taxonomy);
1030
1031 if ($taxonomy) {
1032 $return = sprintf(
1033 '<a href="%1$s">%2$s</a>',
1034 add_query_arg(
1035 [
1036 'page' => 'st_taxonomies',
1037 'add' => 'taxonomy',
1038 'action' => 'edit',
1039 'taxopress_taxonomy' => $taxonomy->name,
1040 ],
1041 taxopress_admin_url('admin.php')
1042 ),
1043 esc_html($taxonomy->labels->name)
1044 );
1045 } else {
1046 $return = '&mdash;';
1047 }
1048
1049 return $return;
1050 }
1051
1052 /**
1053 * Outputs the hidden row displayed when inline editing
1054 *
1055 * @since 3.1.0
1056 */
1057 public function inline_edit()
1058 {
1059 ?>
1060
1061 <form method="get">
1062 <table style="display: none">
1063 <tbody id="inlineedit">
1064
1065 <tr id="inline-edit" class="inline-edit-row" style="display: none">
1066 <td colspan="<?php echo esc_attr($this->get_column_count()); ?>" class="colspanchange">
1067
1068 <fieldset>
1069 <legend class="inline-edit-legend"><?php esc_html_e('Quick Edit', 'simple-tags'); ?></legend>
1070 <div class="inline-edit-col">
1071 <label>
1072 <span class="title"><?php _ex('Name', 'term name', 'simple-tags'); ?></span>
1073 <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span>
1074 </label>
1075
1076 <label>
1077 <span class="title"><?php esc_html_e('Slug', 'simple-tags'); ?></span>
1078 <span class="input-text-wrap"><input type="text" name="slug" class="ptitle" value="" /></span>
1079 </label>
1080 <label>
1081 <span class="taxonomy"><?php _ex('Taxonomy', 'term name', 'simple-tags'); ?></span>
1082
1083 <?php $taxonomies = get_all_taxopress_taxonomies(); ?>
1084 <select class="input-text-wrap edit-tax edit_taxonomy" name="edit_taxonomy">
1085 <?php
1086 foreach ($taxonomies as $taxonomy) {
1087 echo '<option value="' . esc_attr($taxonomy->name) . '">' . esc_html($taxonomy->labels->name) . '</option>';
1088 }
1089 ?>
1090 </select>
1091 </label>
1092 </div>
1093 </fieldset>
1094
1095 <div class="inline-edit-save submit">
1096 <button type="button" class="cancel button alignleft"><?php esc_html_e('Cancel', 'simple-tags'); ?></button>
1097 <button type="button" class="taxopress-save button button-primary alignright"><?php esc_html_e('Update', 'simple-tags'); ?></button>
1098 <span class="spinner"></span>
1099
1100 <?php wp_nonce_field('taxinlineeditnonce', '_inline_edit', false); ?>
1101 <br class="clear" />
1102
1103 <div class="notice notice-error notice-alt inline hidden taxopress-notice">
1104 <p class="error"></p>
1105 </div>
1106 </div>
1107
1108 </td>
1109 </tr>
1110
1111 </tbody>
1112 </table>
1113 </form>
1114 <?php
1115 }
1116 }
1117