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

class.admin.manage.php in Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms 3.54.0, at inc/class.admin.manage.php

1,834 lines 83.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // phpcs:disable Squiz.PHP.CommentedOutCode.Found,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_tax_query,WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Legacy TaxoPress file: keep behavior unchanged while documenting existing PHPCS exceptions.
4
5 class SimpleTags_Admin_Manage
6 {
7 public const MENU_SLUG = 'st_options';
8
9 // class instance
10 public static $instance;
11
12 /**
13 * Constructor
14 *
15 * @return void
16 * @author WebFactory Ltd
17 */
18 public function __construct()
19 {
20 add_filter('set-screen-option', [ __CLASS__, 'set_screen' ], 10, 3);
21 // Admin menu
22 add_action('admin_menu', array( $this, 'admin_menu' ));
23
24 // Register taxo, parent method...
25 SimpleTags_Admin::register_taxonomy();
26
27 // Javascript
28 add_action('admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ), 11);
29
30 //load ajax
31 add_action('wp_ajax_taxopress_check_delete_terms', array( $this, 'handle_taxopress_check_delete_terms_ajax'));
32 add_action('wp_ajax_taxopress_autocomplete_terms', [ $this, 'handle_taxopress_autocomplete_terms']);
33 add_action('wp_ajax_taxopress_merge_terms_preflight', [$this, 'taxopress_merge_terms_preflight']);
34 add_action('wp_ajax_taxopress_merge_terms_batch', [$this, 'taxopress_merge_terms_batch']);
35 add_action('wp_ajax_taxopress_merge_suggestions', [$this, 'handle_taxopress_merge_suggestions']);
36 }
37
38 /**
39 * Init somes JS and CSS need for this feature
40 *
41 * @return void
42 * @author WebFactory Ltd
43 */
44 public static function admin_enqueue_scripts()
45 {
46 wp_register_script('st-helper-manage', STAGS_URL . '/assets/js/helper-manage.js', array( 'jquery' ), STAGS_VERSION);
47
48 // add JS for manage click tags
49 if (isset($_GET['page']) && $_GET['page'] == 'st_manage') {
50 wp_enqueue_script('st-helper-manage');
51 }
52 }
53
54 public static function set_screen($status, $option, $value)
55 {
56 return $value;
57 }
58
59 /**
60 * Add WP admin menu for Tags
61 *
62 * @return void
63 * @author WebFactory Ltd
64 */
65 public function admin_menu()
66 {
67 $hook = add_submenu_page(
68 self::MENU_SLUG,
69 __('TaxoPress: Manage Terms', 'simple-tags'),
70 __('Manage Terms', 'simple-tags'),
71 'simple_tags',
72 'st_manage',
73 array(
74 $this,
75 'page_manage_tags',
76 )
77 );
78 }
79
80 /**
81 * Method for build the page HTML manage tags
82 *
83 * @return void
84 * @author WebFactory Ltd
85 */
86 public function page_manage_tags()
87 {
88 $default_tab = '';
89 // Control Post data
90 if (isset($_POST['term_action'])) {
91 if (!current_user_can('simple_tags')) {
92 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied!', 'simple-tags'), 'error taxopress-notice');
93 } elseif (! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['term_nonce'])), 'simpletags_admin')) { // Origination and intention
94 add_settings_error(__CLASS__, __CLASS__, esc_html__('Security problem. Try again.', 'simple-tags'), 'error taxopress-notice');
95 } elseif (! isset(SimpleTags_Admin::$taxonomy) || ! taxonomy_exists(SimpleTags_Admin::$taxonomy)) { // Valid taxo ?
96 add_settings_error(__CLASS__, __CLASS__, esc_html__('Missing valid taxonomy for work. Try again.', 'simple-tags'), 'error taxopress-notice');
97 } elseif ($_POST['term_action'] == 'renameterm') {
98 $taxonomy = isset($_POST['current_taxo']) ? sanitize_text_field(wp_unslash($_POST['current_taxo'])) : 'post_tag';
99 $post_type = isset($_POST['current_cpt']) ? sanitize_text_field(wp_unslash($_POST['current_cpt'])) : 'post';
100
101 SimpleTags_Admin::$taxonomy = $taxonomy;
102 SimpleTags_Admin::$post_type = $post_type;
103
104 $oldtag = isset($_POST['renameterm_old']) ? sanitize_text_field(wp_unslash($_POST['renameterm_old'])) : '';
105 $newtag = isset($_POST['renameterm_new']) ? sanitize_text_field(wp_unslash($_POST['renameterm_new'])) : '';
106 self::renameTerms($taxonomy, $oldtag, $newtag);
107 $default_tab = '.st-rename-terms';
108 } elseif ($_POST['term_action'] == 'mergeterm') {
109 $taxonomy = isset($_POST['current_taxo']) ? sanitize_text_field(wp_unslash($_POST['current_taxo'])) : 'post_tag';
110 $post_type = isset($_POST['current_cpt']) ? sanitize_text_field(wp_unslash($_POST['current_cpt'])) : 'post';
111
112 SimpleTags_Admin::$taxonomy = $taxonomy;
113 SimpleTags_Admin::$post_type = $post_type;
114
115 $oldtag = isset($_POST['renameterm_old']) ? sanitize_text_field(wp_unslash($_POST['renameterm_old'])) : '';
116 $newtag = isset($_POST['renameterm_new']) ? sanitize_text_field(wp_unslash($_POST['renameterm_new'])) : '';
117 $merge_type = isset($_POST['mergeterm_type']) ? sanitize_text_field(wp_unslash($_POST['mergeterm_type'])) : '';
118 self::mergeTerms($taxonomy, $oldtag, $newtag, $merge_type);
119 $default_tab = '.st-merge-terms';
120 } elseif ($_POST['term_action'] == 'addterm') {
121 $taxonomy = isset($_POST['current_taxo']) ? sanitize_text_field(wp_unslash($_POST['current_taxo'])) : 'post_tag';
122 $post_type = isset($_POST['current_cpt']) ? sanitize_text_field(wp_unslash($_POST['current_cpt'])) : 'post';
123
124 SimpleTags_Admin::$taxonomy = $taxonomy;
125 SimpleTags_Admin::$post_type = $post_type;
126
127 $oldtag = isset($_POST['addterm_match']) ? sanitize_text_field(wp_unslash($_POST['addterm_match'])) : '';
128 $newtag = isset($_POST['addterm_new']) ? sanitize_text_field(wp_unslash($_POST['addterm_new'])) : '';
129 self::addMatchTerms($taxonomy, $oldtag, $newtag);
130 $default_tab = '.st-add-terms';
131 } elseif ($_POST['term_action'] == 'removeterm') {
132 $taxonomy = isset($_POST['current_taxo']) ? sanitize_text_field(wp_unslash($_POST['current_taxo'])) : 'post_tag';
133 $post_type = isset($_POST['current_cpt']) ? sanitize_text_field(wp_unslash($_POST['current_cpt'])) : 'post';
134
135 SimpleTags_Admin::$taxonomy = $taxonomy;
136 SimpleTags_Admin::$post_type = $post_type;
137
138 $matchtag = isset($_POST['removeterm_match']) ? sanitize_text_field(wp_unslash($_POST['removeterm_match'])) : '';
139 $removetag = isset($_POST['remove_term']) ? sanitize_text_field(wp_unslash($_POST['remove_term'])) : '';
140 self::removeMatchTerms($taxonomy, $matchtag, $removetag);
141 $default_tab = '.st-remove-terms';
142 } elseif ($_POST['term_action'] == 'remove-rarelyterms') {
143 $taxonomy = isset($_POST['current_taxo']) ? sanitize_text_field(wp_unslash($_POST['current_taxo'])) : 'post_tag';
144 $post_type = isset($_POST['current_cpt']) ? sanitize_text_field(wp_unslash($_POST['current_cpt'])) : 'post';
145
146 SimpleTags_Admin::$taxonomy = $taxonomy;
147 SimpleTags_Admin::$post_type = $post_type;
148
149 self::removeRarelyUsed($taxonomy, (int) $_POST['number-rarely']);
150 $default_tab = '.st-delete-unuused-terms';
151 } /* elseif ( $_POST['term_action'] == 'editslug' ) {
152
153 $matchtag = (isset($_POST['tagname_match'])) ? $_POST['tagname_match'] : '';
154 $newslug = (isset($_POST['tagslug_new'])) ? $_POST['tagslug_new'] : '';
155 self::editTermSlug( SimpleTags_Admin::$taxonomy, $matchtag, $newslug );
156
157 }*/
158 }
159
160 if ($default_tab && !empty($default_tab)) {
161 //trigger default tab click on load
162 echo '<div class="load-st-default-tab" data-page="' . esc_attr($default_tab) . '"></div>';
163 }
164 $active_tab_slug = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'add-terms';
165
166 // Default order
167 if (! isset($_GET['order'])) {
168 $_GET['order'] = 'name-asc';
169 }
170
171 settings_errors(__CLASS__); ?>
172 <div class="clear"></div>
173 <div class="taxopress-block-wrap">
174 <div class="wrap st_wrap tagcloudui st-manage-terms-page admin-settings wrap st_wrap">
175
176 <h1><?php _e('Manage Terms', 'simple-tags'); ?>
177 </h1>
178 <div class="taxopress-description"><?php esc_html_e('This feature allows you to manage your content terms by adding, renaming, merging and deleting unused terms.', 'simple-tags'); ?></div>
179
180 <div class="clear"></div>
181
182 <hr class="wp-header-end">
183 <div class="clear"></div>
184
185 <div id="col-container" class="wp-clearfix">
186 <div class="col-wrap">
187 <div class="form-wrap">
188 <ul class="simple-tags-nav-tab-wrapper">
189 <li class="nav-tab <?php echo $active_tab_slug === 'add-terms' ? 'nav-tab-active' : ''; ?>" data-page=".st-add-terms">
190 <?php echo esc_html__('Add terms', 'simple-tags'); ?>
191 </li>
192 <li class="nav-tab <?php echo $active_tab_slug === 'remove-terms' ? 'nav-tab-active' : ''; ?>" data-page=".st-remove-terms">
193 <?php echo esc_html__('Remove terms', 'simple-tags'); ?>
194 </li>
195 <li class="nav-tab <?php echo $active_tab_slug === 'rename-terms' ? 'nav-tab-active' : ''; ?>" data-page=".st-rename-terms">
196 <?php echo esc_html__('Rename terms', 'simple-tags'); ?>
197 </li>
198 <li class="nav-tab <?php echo $active_tab_slug === 'merge-terms' ? 'nav-tab-active' : ''; ?>" data-page=".st-merge-terms">
199 <?php echo esc_html__('Merge terms', 'simple-tags'); ?>
200 </li>
201 <li class="nav-tab <?php echo $active_tab_slug === 'delete-unuused-terms' ? 'nav-tab-active' : ''; ?>" data-page=".st-delete-unuused-terms">
202 <?php echo esc_html__('Delete unused terms', 'simple-tags'); ?>
203 </li>
204 </ul>
205 <div class="clear"></div>
206
207
208
209 <table class="form-table">
210
211 <tr valign="top" class="auto-terms-content st-add-terms" style="<?php echo $active_tab_slug === 'add-terms' ? '' : 'display:none;'; ?>">
212 <td>
213 <?php SimpleTags_Admin::tabSelectorTaxonomy('add-terms', 'st_manage'); ?>
214 <h2><?php _e('Add Terms', 'simple-tags'); ?></h2>
215 <p><?php printf(esc_html__('This feature lets you add one or more new terms to all %s which match any of the terms given.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?></p>
216 <p><?php printf(esc_html__('Terms will be added to all %s If no "Term(s) to match" is specified.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?></p>
217
218 <fieldset>
219 <form action="" method="post">
220 <input type="hidden" name="term_action" value="addterm" />
221 <input type="hidden" name="term_nonce" value="<?php echo esc_attr(wp_create_nonce('simpletags_admin')); ?>" />
222 <input type="hidden" name="current_tab" value="add-terms" />
223 <input type="hidden" name="current_taxo" value="<?php echo esc_attr(get_option('add-terms_taxo')); ?>" />
224 <input type="hidden" name="current_cpt" value="<?php echo esc_attr(get_option('add-terms_cpt')); ?>" />
225
226 <p class="terms-type-options">
227 <label>
228 <input type="radio" id="addterm_type" class="addterm_type_all_posts" name="addterm_type" value="all_posts" checked="checked">
229 <?php printf(esc_html__('Add terms to all %s.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?>
230 </label><br>
231 <label>
232 <input type="radio" id="addterm_type" class="addterm_type_matched_only" name="addterm_type" value="matched_only">
233 <?php _e('Add terms only to posts with specific terms attached.', 'simple-tags'); ?>
234 </label>
235 </p>
236
237 <p class="terms-to-maatch-input" style="display: none;">
238 <label for="addterm_match"><?php _e('Term(s) to match:', 'simple-tags'); ?></label><br />
239 <textarea type="text" class="autocomplete-input tag-cloud-input taxopress-expandable-textarea add-terms-autocomplete" id="addterm_match" name="addterm_match" size="80" data-tab="add-terms"
240 data-taxo="<?php echo esc_attr(get_option('add-terms_taxo')); ?>"></textarea>
241 </p>
242
243 <p>
244 <label for="addterm_new"><?php _e('Term(s) to add:', 'simple-tags'); ?></label><br />
245 <textarea type="text" class="autocomplete-input taxopress-expandable-textarea add-terms-autocomplete" id="addterm_new" name="addterm_new" size="80" data-tab="add-terms"
246 data-taxo="<?php echo esc_attr(get_option('add-terms_taxo')); ?>"></textarea>
247 </p>
248
249 <input class="button-primary" type="submit" name="Add" value="<?php _e('Add', 'simple-tags'); ?>" />
250 </form>
251 </fieldset>
252 </td>
253 </tr>
254
255 <tr valign="top" class="auto-terms-content st-remove-terms" style="<?php echo $active_tab_slug === 'remove-terms' ? '' : 'display:none;'; ?>">
256 <td>
257 <?php SimpleTags_Admin::tabSelectorTaxonomy('remove-terms', 'st_manage'); ?>
258 <h2><?php _e('Remove Terms', 'simple-tags'); ?></h2>
259 <p><?php printf(esc_html__('This feature lets you remove one or more terms from all %s which match any of the terms given.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?></p>
260 <p><?php printf(esc_html__('Terms will be removed from all %s If no "Term(s) to match" is specified.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?></p>
261
262 <fieldset>
263 <form action="" method="post">
264 <input type="hidden" name="term_action" value="removeterm" />
265 <input type="hidden" name="term_nonce" value="<?php echo esc_attr(wp_create_nonce('simpletags_admin')); ?>" />
266 <input type="hidden" name="current_tab" value="remove-terms" />
267 <input type="hidden" name="current_taxo" value="<?php echo esc_attr(get_option('remove-terms_taxo')); ?>" />
268 <input type="hidden" name="current_cpt" value="<?php echo esc_attr(get_option('remove-terms_cpt')); ?>" />
269
270 <p class="terms-type-options">
271 <label>
272 <input type="radio" id="removeterm_type" class="removeterm_type_all_posts" name="removeterm_type" value="all_posts" checked="checked">
273 <?php printf(esc_html__('Remove terms from all %s.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?>
274 </label><br>
275 <label>
276 <input type="radio" id="removeterm_type" class="removeterm_type_matched_only" name="removeterm_type" value="matched_only">
277 <?php _e('Remove terms only from posts with specific terms attached.', 'simple-tags'); ?>
278 </label>
279 </p>
280
281 <p class="removeterms-to-match-input" style="display: none;">
282 <label for="removeterm_match"><?php _e('Term(s) to match:', 'simple-tags'); ?></label><br />
283 <textarea type="text" class="autocomplete-input tag-cloud-input taxopress-expandable-textarea remove-terms-autocomplete" id="removeterm_match" name="removeterm_match" size="80" data-tab="remove-terms"
284 data-taxo="<?php echo esc_attr(get_option('remove-terms_taxo')); ?>"></textarea>
285 </p>
286
287 <p>
288 <label for="remove_term"><?php _e('Term(s) to remove:', 'simple-tags'); ?></label><br />
289 <textarea type="text" class="autocomplete-input taxopress-expandable-textarea remove-terms-autocomplete" id="remove_term" name="remove_term" size="80" data-tab="remove-terms"
290 data-taxo="<?php echo esc_attr(get_option('remove-terms_taxo')); ?>"></textarea>
291 </p>
292
293 <input class="button-primary" type="submit" name="Remove" value="<?php _e('Remove', 'simple-tags'); ?>" />
294 </form>
295 </fieldset>
296 </td>
297 </tr>
298
299 <tr valign="top" class="auto-terms-content st-rename-terms" style="<?php echo $active_tab_slug === 'rename-terms' ? '' : 'display:none;'; ?>">
300 <td>
301 <?php SimpleTags_Admin::tabSelectorTaxonomy('rename-terms', 'st_manage'); ?>
302 <h2><?php _e('Rename Terms', 'simple-tags'); ?></h2>
303 <p><?php _e('Enter the terms to rename and their new names.', 'simple-tags'); ?></p>
304
305 <fieldset>
306 <form action="" method="post">
307 <input type="hidden" name="term_action" value="renameterm" />
308 <input type="hidden" name="term_nonce" value="<?php echo esc_attr(wp_create_nonce('simpletags_admin')); ?>" />
309 <input type="hidden" name="current_tab" value="rename-terms" />
310 <input type="hidden" name="current_taxo" value="<?php echo esc_attr(get_option('rename-terms_taxo')); ?>" />
311 <input type="hidden" name="current_cpt" value="<?php echo esc_attr(get_option('rename-terms_cpt')); ?>" />
312 <p>
313 <label for="renameterm_old"><?php _e('Term(s) to rename:', 'simple-tags'); ?></label><br />
314 <textarea type="text" class="autocomplete-input tag-cloud-input taxopress-expandable-textarea rename-terms-autocomplete" id="renameterm_old" name="renameterm_old" size="80" data-taxo="<?php echo esc_attr(get_option('rename-terms_taxo')); ?>"></textarea>
315 </p>
316
317 <p>
318 <label for="renameterm_new"><?php _e('New term name(s):', 'simple-tags'); ?></label><br />
319 <textarea type="text" class="autocomplete-input taxopress-expandable-textarea rename-terms-autocomplete" id="renameterm_new" name="renameterm_new" size="80" data-taxo="<?php echo esc_attr(get_option('rename-terms_taxo')); ?>"></textarea>
320 </p>
321
322 <input class="button-primary" type="submit" name="rename" value="<?php _e('Rename', 'simple-tags'); ?>" />
323 </form>
324 </fieldset>
325 </td>
326 </tr>
327
328 <tr valign="top" class="auto-terms-content st-merge-terms" style="<?php echo $active_tab_slug === 'merge-terms' ? '' : 'display:none;'; ?>">
329 <td>
330 <?php SimpleTags_Admin::tabSelectorTaxonomy('merge-terms', 'st_manage'); ?>
331 <h2><?php _e('Merge Terms', 'simple-tags'); ?></h2>
332 <p><?php esc_html_e('This feature will delete existing terms and replace them with another term.', 'simple-tags'); ?></p>
333
334 <fieldset>
335 <form action="" method="post" class="merge-terms-form">
336 <input type="hidden" name="term_action" value="mergeterm" />
337 <input type="hidden" name="term_nonce" value="<?php echo esc_attr(wp_create_nonce('simpletags_admin')); ?>" />
338 <input type="hidden" name="current_tab" value="merge-terms" />
339 <input type="hidden" name="current_taxo" value="<?php echo esc_attr(get_option('merge-terms_taxo')); ?>" />
340 <input type="hidden" name="current_cpt" value="<?php echo esc_attr(get_option('merge-terms_cpt')); ?>" />
341
342 <p class="terms-type-options">
343 <label><input type="radio" id="mergeterm_type" class="mergeterm_type_different_name" name="mergeterm_type" value="different_name" checked="checked"><?php _e('Merge terms with different name.', 'simple-tags'); ?></label><br>
344 <label><input type="radio" id="mergeterm_type" class="mergeterm_type_same_name" name="mergeterm_type" value="same_name"><?php esc_html_e('Merge terms with same name.', 'simple-tags'); ?></label>
345 </p>
346
347 <p>
348 <label for="renameterm_old"><?php _e('Term(s) to merge.', 'simple-tags'); ?></label><br />
349 <textarea type="text" class="autocomplete-input tag-cloud-input taxopress-expandable-textarea merge-feature-autocomplete" id="mergeterm_old" name="renameterm_old" size="80" data-taxo="<?php echo esc_attr(get_option('merge-terms_taxo')); ?>"></textarea>
350 </p>
351
352 <p class="new_name_input">
353 <label for="renameterm_new"><?php _e('New term. The Old terms will be deleted and any posts assigned to the old terms will be re-assigned to this term.', 'simple-tags'); ?></label><br />
354 <textarea type="text" class="autocomplete-input taxopress-expandable-textarea merge-feature-autocomplete" id="mergeterm_new" name="renameterm_new" size="80" data-taxo="<?php echo esc_attr(get_option('merge-terms_taxo')); ?>"></textarea>
355 </p>
356
357 <input class="suggest-merge-terms" type="button" id="suggest-merge-terms" value="<?php _e('Suggest Terms to Merge', 'simple-tags'); ?>" />
358 <input class="button-primary" type="submit" name="merge" id="merge-terms" value="<?php _e('Merge Terms', 'simple-tags'); ?>" />
359 <div id="merge-progress" style="margin-top: 10px;"></div>
360 </form>
361 </fieldset>
362 </td>
363 </tr>
364
365 <tr valign="top" class="auto-terms-content st-delete-unuused-terms" style="<?php echo $active_tab_slug === 'delete-unuused-terms' ? '' : 'display:none;'; ?>">
366 <td>
367 <?php SimpleTags_Admin::tabSelectorTaxonomy('delete-unuused-terms', 'st_manage'); ?>
368 <h2><?php esc_html_e('Remove rarely used terms', 'simple-tags'); ?></h2>
369 <p><?php esc_html_e('This feature allows you to remove rarely used terms.', 'simple-tags'); ?></p>
370 <p><?php printf(esc_html__('If you choose 5, Taxopress will delete all terms attached to less than 5 %s.', 'simple-tags'), esc_html(SimpleTags_Admin::$post_type_name)); ?></p>
371
372 <fieldset>
373 <form action="" method="post">
374 <input type="hidden" name="term_action" value="remove-rarelyterms" />
375 <input type="hidden" name="term_nonce" value="<?php echo esc_attr(wp_create_nonce('simpletags_admin')); ?>" />
376 <input type="hidden" name="current_tab" value="delete-unuused-terms" />
377 <input type="hidden" name="current_taxo" value="<?php echo esc_attr(get_option('delete-unuused-terms_taxo')); ?>" />
378 <input type="hidden" name="current_cpt" value="<?php echo esc_attr(get_option('delete-unuused-terms_cpt')); ?>" />
379
380 <p>
381 <label for="number-delete"><?php _e('Minimum number of uses for each term:', 'simple-tags'); ?></label><br />
382 <select name="number-rarely" id="number-delete">
383 <?php for ($i = 1; $i <= 100; $i++) : ?>
384 <option value="<?php echo esc_attr($i); ?>"><?php echo esc_html($i); ?></option>
385 <?php endfor; ?>
386 </select>
387 </p>
388
389 <label for="check-terms"><?php _e('Check how many terms will be deleted:', 'simple-tags'); ?></label><br />
390 <input style="margin-top: 2px;" id="check-terms-btn" class="button-primary" type="submit" name="Check" value="<?php esc_attr_e('Check Terms', 'simple-tags'); ?>" />
391 <div id="terms-feedback"></div>
392
393 <label for="terms-delete"><?php _e('Delete rarely used terms:', 'simple-tags'); ?></label><br />
394 <input style="margin-top: 2px;" class="button-secondary delete-unused-term" type="submit" name="Delete" value="<?php esc_attr_e('Delete Terms', 'simple-tags'); ?>" />
395 </form>
396 </fieldset>
397 </td>
398 </tr>
399
400 <?php /*
401 <tr valign="top">
402 <th scope="row"><strong><?php _e('Edit Term Slug', 'simple-tags'); ?></strong></th>
403 <td>
404 <p><?php _e('Enter the term name to edit and its new slug. <a href="http://codex.wordpress.org/Glossary#Slug">Slug definition</a>', 'simple-tags'); ?></p>
405
406 <fieldset>
407 <form action="" method="post">
408 <input type="hidden" name="taxo" value="<?php echo esc_attr(SimpleTags_Admin::$taxonomy); ?>" />
409 <input type="hidden" name="cpt" value="<?php echo esc_attr(SimpleTags_Admin::$post_type); ?>" />
410
411 <input type="hidden" name="term_action" value="editslug" />
412 <input type="hidden" name="term_nonce" value="<?php echo wp_create_nonce('simpletags_admin'); ?>" />
413
414 <p>
415 <label for="tagname_match"><?php _e('Term(s) to match:', 'simple-tags'); ?></label>
416 <br />
417 <input type="text" class="autocomplete-input" id="tagname_match" name="tagname_match" size="80" />
418 </p>
419
420 <p>
421 <label for="tagslug_new"><?php _e('Slug(s) to set:', 'simple-tags'); ?></label>
422 <br />
423 <input type="text" class="autocomplete-input" id="tagslug_new" name="tagslug_new" size="80" />
424 </p>
425
426 <input class="button-primary" type="submit" name="edit" value="<?php _e('Edit', 'simple-tags'); ?>" />
427 </form>
428 </fieldset>
429 </td>
430 </tr>
431 */
432 ?>
433
434 </table>
435
436 <div class="remodal" data-remodal-id="taxopress-modal-merge-warning"
437 data-remodal-options="hashTracking: false, closeOnOutsideClick: false">
438 <div class="taxopress-merge-warning-title">
439 <?php echo esc_html__('Multiple merge suggestions selected', 'simple-tags'); ?>
440 </div>
441 <div id="taxopress-modal-merge-warning-content">
442 <?php echo esc_html__(
443 'You have selected multiple merge suggestions. All selected terms will be merged into a single new term.',
444 'simple-tags'
445 ); ?>
446 </div>
447 <br>
448 <button id="taxopress-merge-warning-cancel" data-remodal-action="cancel" class="button button-secondary remodal-cancel">
449 <?php echo esc_html__('Cancel', 'simple-tags'); ?>
450 </button>
451 <button id="taxopress-merge-warning-confirm" class="button button-primary">
452 <?php echo esc_html__('Continue', 'simple-tags'); ?>
453 </button>
454 </div>
455
456
457
458 </div>
459 </div>
460
461
462 <div class="clear"></div>
463
464 </div>
465 </div>
466 <div class="taxopress-right-sidebar admin-settings-sidebar">
467 <?php do_action('taxopress_admin_after_sidebar'); ?>
468 </div>
469
470 </div>
471
472
473 <?php SimpleTags_Admin::printAdminFooter(); ?>
474
475
476
477 <?php
478 do_action('simpletags-manage_terms', SimpleTags_Admin::$taxonomy);
479 }
480
481 /**
482 * Method to merge tags
483 *
484 * @param string $taxonomy
485 * @param string $old
486 * @param string $new
487 *
488 * @return boolean
489 * @author olatechpro
490 */
491 public static function mergeTerms($taxonomy = 'post_tag', $old = '', $new = '', $merge_type = '')
492 {
493 $taxonomy = sanitize_key($taxonomy);
494 if (
495 !taxopress_current_user_can_for_taxonomy(
496 $taxonomy,
497 ['edit_terms', 'delete_terms', 'assign_terms']
498 )
499 ) {
500 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
501 return false;
502 }
503
504 // Helper function to extract term name (ignoring slug in brackets)
505 $extractTermName = function ($term) {
506 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
507 };
508
509 if ($merge_type === 'same_name') {
510 $old_terms = explode(',', $old);
511 $old_terms = array_map($extractTermName, $old_terms);
512 $old_terms = array_filter($old_terms, '_delete_empty_element');
513 $old_terms = array_values(array_unique(array_map('sanitize_text_field', $old_terms)));
514 $retained_slug_param = isset($_POST['retained_slug']) ? sanitize_title(wp_unslash($_POST['retained_slug'])) : '';
515
516 if (empty($old_terms)) {
517 add_settings_error(__CLASS__, __CLASS__, esc_html__('No terms provided for merging!', 'simple-tags'), 'error taxopress-notice');
518 return false;
519 }
520
521 $terms = [];
522 $terms_by_id = [];
523 foreach ($old_terms as $term_name) {
524 $term_objects = get_terms([
525 'taxonomy' => $taxonomy,
526 'name' => sanitize_text_field($term_name),
527 'hide_empty' => false,
528 ]);
529
530 if (is_array($term_objects) && count($term_objects) > 1) {
531 foreach ($term_objects as $term_object) {
532 $terms_by_id[(int) $term_object->term_id] = $term_object;
533 }
534 }
535 }
536
537 $terms = array_values($terms_by_id);
538
539 if (empty($terms)) {
540 add_settings_error(__CLASS__, __CLASS__, esc_html__('No terms with the same name found.', 'simple-tags'), 'error taxopress-notice');
541 return false;
542 }
543
544 $sort_term_name = reset($old_terms);
545 usort($terms, function ($a, $b) use ($sort_term_name, $retained_slug_param) {
546 // If a retained slug is provided, sort that term to the top
547 if ($retained_slug_param) {
548 if ($a->slug === $retained_slug_param) {
549 return -1;
550 }
551 if ($b->slug === $retained_slug_param) {
552 return 1;
553 }
554 }
555 // Score based on similarity to term name
556 similar_text($sort_term_name, $a->slug, $similarity_a);
557 similar_text($sort_term_name, $b->slug, $similarity_b);
558
559 if ($similarity_a === $similarity_b) {
560 // If similarity is the same, sort by length (shortest first)
561 return strlen($a->slug) - strlen($b->slug);
562 }
563
564 // Otherwise, sort by similarity (higher first)
565 return $similarity_b - $similarity_a;
566 });
567
568 // Retain the term with the highest similarity and shortest slug (first in sorted array)
569 $retained_term = $terms[0];
570 $retained_slug = $retained_term->slug;
571 $retained_id = $retained_term->term_id;
572
573 // Collect terms to delete
574 $terms_to_delete = array_filter($terms, function ($term) use ($retained_id) {
575 return $term->term_id !== $retained_id;
576 });
577
578 // Reassign objects and delete old terms (existing logic)
579 $terms_id = array_map(function ($term) {
580 return $term->term_id;
581 }, $terms_to_delete);
582
583 $objects_id = get_objects_in_term($terms_id, $taxonomy, ['fields' => 'ids']);
584 foreach ($objects_id as $object_id) {
585 if (!current_user_can('edit_post', $object_id)) {
586 continue;
587 }
588 // Check if the object already has the term assigned
589 $current_terms = wp_get_object_terms($object_id, $taxonomy, ['fields' => 'ids']);
590 if (!in_array($retained_id, $current_terms)) {
591 wp_set_object_terms($object_id, $retained_slug, $taxonomy, true);
592 }
593 }
594
595 foreach ($terms_to_delete as $term) {
596 wp_delete_term($term->term_id, $taxonomy);
597 }
598
599 // Clean caches
600 clean_object_term_cache($objects_id, $taxonomy);
601 clean_term_cache($terms_id, $taxonomy);
602
603 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Merged term(s) with the same name "%s". %d posts updated.', 'simple-tags'), $retained_term->name, count($objects_id)), 'updated taxopress-notice');
604 return [
605 'success' => true,
606 'retained_slug' => $retained_slug,
607 'posts_updated' => count($objects_id),
608 'post_ids' => $objects_id,
609 'terms_merged' => count($terms_to_delete) + 1,
610 ];
611 } else {
612 if (trim(str_replace(',', '', stripslashes($new))) == '' && $merge_type !== 'same_name') {
613 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new term specified!', 'simple-tags'), 'error taxopress-notice');
614
615 return false;
616 }
617
618 // String to array
619 $old_terms = explode(',', $old);
620 $new_terms = explode(',', $new);
621
622 $old_terms = array_map($extractTermName, $old_terms);
623 $new_terms = array_map($extractTermName, $new_terms);
624
625 // Remove empty element and trim
626 $old_terms = array_filter($old_terms, '_delete_empty_element');
627 $new_terms = array_filter($new_terms, '_delete_empty_element');
628 $old_terms = array_values(array_unique(array_map('sanitize_text_field', $old_terms)));
629 $new_terms = array_values(array_unique(array_map('sanitize_text_field', $new_terms)));
630 $common_elements = array_intersect($old_terms, $new_terms);
631
632 // If old/new tag are empty => exit !
633 if (empty($old_terms) || empty($new_terms)) {
634 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new/old valid term specified!', 'simple-tags'), 'error taxopress-notice');
635
636 return false;
637 }
638
639 if (!empty($common_elements) && $merge_type !== 'different_name') {
640 add_settings_error(__CLASS__, __CLASS__, esc_html__('Term to merge and New Term must not contain same term.', 'simple-tags'), 'error taxopress-notice');
641
642 return false;
643 }
644
645 $counter = 0;
646 $unique_objects = [];
647 $terms_id = [];
648 if (count($new_terms) == 1) { // Merge
649 // Set new tag
650 $new_tag = sanitize_text_field($new_terms[0]);
651 if (empty($new_tag)) {
652 add_settings_error(__CLASS__, __CLASS__, esc_html__('No valid new term.', 'simple-tags'), 'error taxopress-notice');
653
654 return false;
655 }
656
657 // Resolve the source terms before creating the destination term.
658 $source_terms = [];
659 foreach ((array) $old_terms as $old_tag) {
660 $old_tag = sanitize_text_field($old_tag);
661 $term = get_term_by('name', $old_tag, $taxonomy);
662 if (!$term || is_wp_error($term)) {
663 $term = get_term_by('slug', sanitize_title($old_tag), $taxonomy);
664 }
665
666 if ($term && !is_wp_error($term)) {
667 $source_terms[(int) $term->term_id] = $term;
668 }
669 }
670
671 if (empty($source_terms)) {
672 add_settings_error(__CLASS__, __CLASS__, esc_html__('No matching terms found to merge.', 'simple-tags'), 'error taxopress-notice');
673 return false;
674 }
675
676 // Ensure the destination term exists only after valid source terms are found.
677 $new_term = get_term_by('name', $new_tag, $taxonomy);
678 if (!$new_term || is_wp_error($new_term)) {
679 $new_term = get_term_by('slug', sanitize_title($new_tag), $taxonomy);
680 }
681
682 if (!$new_term || is_wp_error($new_term)) {
683 $new_term_info = wp_insert_term($new_tag, $taxonomy);
684 if (is_wp_error($new_term_info)) {
685 add_settings_error(__CLASS__, __CLASS__, esc_html__('Failed to create the new term.', 'simple-tags'), 'error taxopress-notice');
686 return false;
687 }
688 $new_term_id = (int) $new_term_info['term_id'];
689 } else {
690 $new_term_id = (int) $new_term->term_id;
691 }
692
693 // Never delete the destination when it was also selected as a source term.
694 unset($source_terms[$new_term_id]);
695
696 if (empty($source_terms)) {
697 add_settings_error(__CLASS__, __CLASS__, esc_html__('No other matching terms found to merge.', 'simple-tags'), 'error taxopress-notice');
698 return false;
699 }
700
701 $terms_id = array_keys($source_terms);
702 $found_terms = wp_list_pluck(array_values($source_terms), 'name');
703
704 // Get objects from terms ID
705 $objects_id = get_objects_in_term($terms_id, $taxonomy, ['fields' => 'ids']);
706
707 // Use a set to track unique post IDs
708 $unique_objects = [];
709
710 // Assign the new term to all posts associated with the old terms
711 foreach ((array) $objects_id as $object_id) {
712 if (!current_user_can('edit_post', $object_id)) {
713 continue;
714 }
715 // Check if the object already has the term assigned
716 $current_terms = wp_get_object_terms($object_id, $taxonomy, ['fields' => 'ids']);
717 if (!in_array($new_term_id, $current_terms)) {
718 wp_set_object_terms($object_id, $new_term_id, $taxonomy, true);
719 }
720 $unique_objects[$object_id] = true; // Add to unique set
721 }
722
723 // Count unique posts
724 $counter = count($unique_objects);
725
726 // Delete old terms
727 foreach ((array) $terms_id as $term_id) {
728 wp_delete_term($term_id, $taxonomy);
729 }
730
731 // Test if term is also a category
732 /*
733 if ( is_term($new_tag, 'category') ) {
734 // Edit the slug to use the new term
735 self::editTermSlug( $new_tag, sanitize_title($new_tag) );
736 }
737 */
738
739 // Clean cache
740 clean_object_term_cache($objects_id, $taxonomy);
741 clean_term_cache($terms_id, $taxonomy);
742
743
744 if (empty($found_terms)) {
745 add_settings_error(__CLASS__, __CLASS__, esc_html__('No terms were merged (terms may not exist).', 'simple-tags'), 'error taxopress-notice');
746 } else {
747 $message = sprintf(
748 esc_html__('Successfully merged %1$d term(s) "%2$s" to "%3$s". %4$s posts updated.', 'simple-tags'),
749 count($found_terms),
750 implode(', ', $found_terms),
751 rtrim($new, ','),
752 $counter
753 );
754 add_settings_error(__CLASS__, __CLASS__, $message, 'updated taxopress-notice');
755 }
756 } else { // Error
757 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Error. You need to enter a single term to merge to in new term name !', 'simple-tags'), $old), 'error taxopress-notice');
758 return false;
759 }
760
761 return [
762 'success' => true,
763 'merged_into' => $new_tag,
764 'posts_updated' => $counter,
765 'post_ids' => array_keys($unique_objects),
766 'terms_merged' => count($terms_id) + 1,
767 ];
768 }
769 }
770
771 /**
772 * Find terms with same names but different slugs
773 */
774 public static function getSameNameMergeSuggestions($taxonomy = 'post_tag')
775 {
776 global $wpdb;
777
778 $query = $wpdb->prepare("
779 SELECT t.name, COUNT(*) as count, GROUP_CONCAT(CONCAT(t.name, ' (', t.slug, ')') SEPARATOR ', ') as terms
780 FROM {$wpdb->terms} t
781 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
782 WHERE tt.taxonomy = %s
783 GROUP BY t.name
784 HAVING COUNT(*) > 1
785 ORDER BY count DESC, t.name ASC
786 ", $taxonomy);
787
788 $results = $wpdb->get_results($query);
789
790 $show_slug = (int) SimpleTags_Plugin::get_option_value('enable_merge_terms_slug');
791
792 if (!$show_slug) {
793 foreach ($results as &$result) {
794 $result->terms = preg_replace('/\s*\([^)]+\)/', '', $result->terms);
795 }
796 }
797
798 return $results;
799 }
800
801 /**
802 * Find terms that might be candidates for merging based on similarity
803 */
804 public static function getDifferentNameMergeSuggestions($taxonomy = 'post_tag', $limit = 50)
805 {
806 global $wpdb;
807
808 $limit = min(max((int) $limit, 1), 50);
809
810 $terms = get_terms([
811 'taxonomy' => $taxonomy,
812 'hide_empty' => false,
813 'number' => $limit * 2,
814 'update_term_meta_cache' => false,
815 ]);
816
817 $show_slug = (int) SimpleTags_Plugin::get_option_value('enable_merge_terms_slug');
818
819 $suggestions = [];
820
821 foreach ($terms as $i => $term1) {
822 foreach ($terms as $j => $term2) {
823 if ($i >= $j) {
824 continue;
825 }
826
827 $similarity_score = 0;
828 $reasons = [];
829
830 // 1. Levenshtein distance (for typos)
831 $term1_name = strtolower($term1->name);
832 $term2_name = strtolower($term2->name);
833 if (abs(strlen($term1_name) - strlen($term2_name)) <= 2 && max(strlen($term1_name), strlen($term2_name)) <= 100) {
834 $levenshtein = levenshtein($term1_name, $term2_name);
835 if ($levenshtein <= 2 && $levenshtein > 0) {
836 $similarity_score += 40;
837 $reasons[] = esc_html__('Similar spelling', 'simple-tags');
838 }
839 }
840
841 // 2. Soundex (phonetic similarity)
842 if (soundex($term1->name) === soundex($term2->name)) {
843 $similarity_score += 30;
844 $reasons[] = esc_html__('Sounds similar', 'simple-tags');
845 }
846
847 // 3. Common words/partial matches
848 $words1 = explode(' ', strtolower($term1->name));
849 $words2 = explode(' ', strtolower($term2->name));
850 $common_words = array_intersect($words1, $words2);
851 if (!empty($common_words)) {
852 $similarity_score += count($common_words) * 15;
853 $reasons[] = sprintf(esc_html__('Shared words: %s', 'simple-tags'), implode(', ', $common_words));
854 }
855
856 // 4. Prefix/suffix similarity
857 if (strpos($term1->name, $term2->name) !== false || strpos($term2->name, $term1->name) !== false) {
858 $similarity_score += 25;
859 $reasons[] = esc_html__('One contains the other', 'simple-tags');
860 }
861
862 if ($similarity_score >= 30) {
863 $suggested_name = '';
864
865 // Get clean term names for comparison
866 $term1_clean = strtolower(trim($term1->name));
867 $term2_clean = strtolower(trim($term2->name));
868
869 if (!empty($common_words)) {
870 $suggested_name = ucfirst(implode(' ', $common_words));
871 } else {
872 // Use the shorter term as base
873 $suggested_name = strlen($term1->name) <= strlen($term2->name) ? $term1->name : $term2->name;
874 }
875
876 // let's make sure suggested name is different from both original terms
877 $suggested_clean = strtolower(trim($suggested_name));
878 if ($suggested_clean === $term1_clean || $suggested_clean === $term2_clean) {
879 if (!empty($common_words)) {
880 $suggested_name = ucfirst($common_words[0]);
881 } else {
882 $popular_term = ($term1->count >= $term2->count) ? $term1 : $term2;
883 $suggested_name = $popular_term->name;
884 }
885
886 $suggested_clean = strtolower(trim($suggested_name));
887 }
888
889 $term1_display = $show_slug ? $term1->name . ' (' . $term1->slug . ')' : $term1->name;
890 $term2_display = $show_slug ? $term2->name . ' (' . $term2->slug . ')' : $term2->name;
891
892 $suggestions[] = [
893 'term1' => $term1_display,
894 'term2' => $term2_display,
895 'score' => $similarity_score,
896 'reasons' => implode(', ', $reasons),
897 'suggested_name' => $suggested_name
898 ];
899 }
900 }
901 }
902
903 usort($suggestions, function ($a, $b) {
904 return $b['score'] - $a['score'];
905 });
906
907 return array_slice($suggestions, 0, $limit);
908 }
909
910 /**
911 * AJAX handler for merge suggestions
912 */
913 public function handle_taxopress_merge_suggestions()
914 {
915 if (!current_user_can('simple_tags')) {
916 wp_send_json_error('Permission denied');
917 }
918
919 if (!wp_verify_nonce(wp_unslash($_POST['nonce']), 'simpletags_admin')) {
920 wp_send_json_error('Invalid nonce');
921 }
922
923 $taxonomy = get_option('merge-terms_taxo', 'post_tag');
924 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'manage_terms')) {
925 wp_send_json_error('Permission denied', 403);
926 }
927 $merge_type = sanitize_text_field(wp_unslash($_POST['merge_type']));
928
929 if ($merge_type === 'same_name') {
930 $suggestions = self::getSameNameMergeSuggestions($taxonomy);
931 wp_send_json_success([
932 'type' => 'same_name',
933 'suggestions' => $suggestions
934 ]);
935 } else {
936 $suggestions = self::getDifferentNameMergeSuggestions($taxonomy);
937 wp_send_json_success([
938 'type' => 'different_name',
939 'suggestions' => $suggestions
940 ]);
941 }
942 }
943
944 private static function get_merge_preflight_term_ids($taxonomy, $old_terms_input, $merge_type)
945 {
946 $extractTermName = function ($term) {
947 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
948 };
949
950 $term_ids = [];
951
952 foreach ($old_terms_input as $term_name) {
953 $term_name_clean = sanitize_text_field($extractTermName($term_name));
954
955 if (empty($term_name_clean)) {
956 continue;
957 }
958
959 if ($merge_type === 'same_name') {
960 $matching_terms = get_terms([
961 'taxonomy' => $taxonomy,
962 'name' => $term_name_clean,
963 'hide_empty' => false,
964 'fields' => 'ids',
965 'update_term_meta_cache' => false,
966 ]);
967
968 if (!is_wp_error($matching_terms) && !empty($matching_terms)) {
969 foreach ($matching_terms as $term_id) {
970 $term_ids[] = (int) $term_id;
971 }
972
973 continue;
974 }
975 }
976
977 $term = get_term_by('name', $term_name_clean, $taxonomy);
978 if (!$term || is_wp_error($term)) {
979 $term = get_term_by('slug', sanitize_title($term_name_clean), $taxonomy);
980 }
981
982 if ($term && !is_wp_error($term)) {
983 $term_ids[] = (int) $term->term_id;
984 }
985 }
986
987 return array_values(array_unique(array_filter($term_ids)));
988 }
989
990 private static function count_objects_for_merge_terms($taxonomy, $term_ids)
991 {
992 global $wpdb;
993
994 if (empty($term_ids)) {
995 return 0;
996 }
997
998 $term_ids = array_values(array_unique(array_map('intval', $term_ids)));
999 $cache_key = 'merge_preflight_' . md5($taxonomy . ':' . implode(',', $term_ids));
1000 $cached_count = wp_cache_get($cache_key, 'taxopress_terms');
1001
1002 if ($cached_count !== false) {
1003 return (int) $cached_count;
1004 }
1005
1006 $term_id_placeholders = implode(',', array_fill(0, count($term_ids), '%d'));
1007 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1008 $term_taxonomy_query = $wpdb->prepare(
1009 "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s AND term_id IN ({$term_id_placeholders})",
1010 array_merge([$taxonomy], $term_ids)
1011 );
1012
1013 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1014 $term_taxonomy_ids = $wpdb->get_col($term_taxonomy_query);
1015
1016 if (empty($term_taxonomy_ids)) {
1017 wp_cache_set($cache_key, 0, 'taxopress_terms', MINUTE_IN_SECONDS);
1018 return 0;
1019 }
1020
1021 $term_taxonomy_ids = array_values(array_unique(array_map('intval', $term_taxonomy_ids)));
1022 $term_taxonomy_id_placeholders = implode(',', array_fill(0, count($term_taxonomy_ids), '%d'));
1023 $object_count_query = $wpdb->prepare(
1024 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1025 "SELECT COUNT(DISTINCT object_id) FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ({$term_taxonomy_id_placeholders})",
1026 $term_taxonomy_ids
1027 );
1028
1029 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1030 $object_count = (int) $wpdb->get_var($object_count_query);
1031
1032 wp_cache_set($cache_key, $object_count, 'taxopress_terms', MINUTE_IN_SECONDS);
1033
1034 return $object_count;
1035 }
1036
1037 public static function taxopress_merge_terms_preflight()
1038 {
1039 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
1040 if (empty($nonce) || !wp_verify_nonce($nonce, 'st-admin-js')) {
1041 wp_send_json_error(['message' => __('Security check failed.', 'simple-tags')], 403);
1042 wp_die();
1043 }
1044
1045 if (!current_user_can('simple_tags')) {
1046 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1047 wp_die();
1048 }
1049
1050 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : '';
1051 if (empty($taxonomy) || !taxonomy_exists($taxonomy)) {
1052 wp_send_json_error(['message' => __('Invalid taxonomy.', 'simple-tags')], 400);
1053 wp_die();
1054 }
1055
1056 if (
1057 !taxopress_current_user_can_for_taxonomy(
1058 $taxonomy,
1059 ['edit_terms', 'delete_terms', 'assign_terms']
1060 )
1061 ) {
1062 wp_send_json_error(['message' => __('Permission denied for this taxonomy.', 'simple-tags')], 403);
1063 wp_die();
1064 }
1065
1066 $merge_type = isset($_POST['merge_type']) ? sanitize_text_field(wp_unslash($_POST['merge_type'])) : 'different_name';
1067 $old_terms_input = isset($_POST['old_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_POST['old_terms'])) : [];
1068
1069 $term_ids = self::get_merge_preflight_term_ids($taxonomy, $old_terms_input, $merge_type);
1070 $affected_posts_count = self::count_objects_for_merge_terms($taxonomy, $term_ids);
1071 $workload = count($term_ids) * $affected_posts_count;
1072 $ajax_workload_threshold = (int) apply_filters('taxopress_merge_terms_ajax_workload_threshold', 20);
1073
1074 wp_send_json_success([
1075 'terms_count' => count($term_ids),
1076 'affected_posts_count' => $affected_posts_count,
1077 'workload' => $workload,
1078 'use_ajax' => $workload > $ajax_workload_threshold,
1079 ]);
1080 }
1081
1082 public static function taxopress_merge_terms_batch()
1083 {
1084 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
1085 if (empty($nonce) || !wp_verify_nonce($nonce, 'st-admin-js')) {
1086 wp_send_json_error(['message' => __('Security check failed.', 'simple-tags')], 403);
1087 wp_die();
1088 }
1089
1090 if (!current_user_can('simple_tags')) {
1091 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1092 wp_die();
1093 }
1094
1095 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : '';
1096 $new_term = isset($_POST['new_term']) ? sanitize_text_field(wp_unslash($_POST['new_term'])) : '';
1097 $merge_type = isset($_POST['merge_type']) ? sanitize_text_field(wp_unslash($_POST['merge_type'])) : 'different_name';
1098 $old_terms_input = isset($_POST['old_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_POST['old_terms'])) : [];
1099
1100 if (
1101 !taxopress_current_user_can_for_taxonomy(
1102 $taxonomy,
1103 ['edit_terms', 'delete_terms', 'assign_terms']
1104 )
1105 ) {
1106 wp_send_json_error(['message' => __('Permission denied for this taxonomy.', 'simple-tags')], 403);
1107 wp_die();
1108 }
1109
1110 $extractTermName = function ($term) {
1111 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1112 };
1113
1114 $old_terms = [];
1115
1116 foreach ($old_terms_input as $term_name) {
1117 $term_name_clean = $extractTermName($term_name);
1118 $term = get_term_by('name', $term_name_clean, $taxonomy);
1119 if (!$term || is_wp_error($term)) {
1120 $term = get_term_by('slug', sanitize_title($term_name_clean), $taxonomy);
1121 }
1122 if ($term && !is_wp_error($term)) {
1123 $old_terms[] = $term->name;
1124 }
1125 }
1126
1127 $old_terms = array_values(array_unique($old_terms));
1128
1129 if (empty($old_terms)) {
1130 wp_send_json_error([
1131 'message' => __('Please enter a valid term. Merge cancelled.', 'simple-tags')
1132 ]);
1133 wp_die();
1134 }
1135
1136 if ($merge_type === 'different_name' && !empty($new_term)) {
1137 $old_terms = array_values(array_filter($old_terms, function ($term_name) use ($new_term) {
1138 return strtolower(trim($term_name)) !== strtolower(trim($new_term));
1139 }));
1140 }
1141
1142
1143 // Execute the merge
1144 $result = SimpleTags_Admin_Manage::mergeTerms($taxonomy, implode(',', $old_terms), $new_term, $merge_type);
1145
1146 if ($result === true || (is_array($result) && !empty($result['success']))) {
1147 $response = ['message' => __('Merge successful', 'simple-tags')];
1148
1149 if (is_array($result)) {
1150 $response = array_merge($response, $result);
1151 }
1152
1153 wp_send_json_success($response);
1154 } else {
1155 global $wp_settings_errors;
1156 $errors = [];
1157
1158 if (!empty($wp_settings_errors)) {
1159 foreach ($wp_settings_errors as $error) {
1160 if (!empty($error['message'])) {
1161 $errors[] = $error['message'];
1162 }
1163 }
1164 }
1165
1166 if (is_wp_error($result)) {
1167 $errors[] = $result->get_error_message();
1168 } elseif (is_array($result) && isset($result['message'])) {
1169 $errors[] = $result['message'];
1170 }
1171
1172 wp_send_json_error(['message' => implode(' ', $errors)]);
1173 }
1174
1175 wp_die();
1176 }
1177
1178 /**
1179 * Method for remove tags
1180 *
1181 * @param string $taxonomy
1182 * @param string $post_type
1183 * @param string $tag
1184 *
1185 * @return boolean
1186 * @author WebFactory Ltd
1187 */
1188 public static function removeTerms($taxonomy = 'post_tag', $post_type = 'posts', $new = '')
1189 {
1190 $taxonomy = sanitize_key($taxonomy);
1191 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'assign_terms')) {
1192 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1193 return false;
1194 }
1195
1196 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1197 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term specified!', 'simple-tags'), 'error taxopress-notice');
1198
1199 return false;
1200 }
1201
1202 // String to array
1203 $new_terms = explode(',', $new);
1204
1205 // Remove empty element and trim
1206 $new_terms = array_filter($new_terms, '_delete_empty_element');
1207
1208 // If new tag are empty => exit !
1209 if (empty($new_terms)) {
1210 add_settings_error(__CLASS__, __CLASS__, esc_html__('No valid term specified!', 'simple-tags'), 'error taxopress-notice');
1211
1212 return false;
1213 }
1214
1215 $counter = 0;
1216 if (count($new_terms) > 0) {
1217 foreach ((array) $new_terms as $term) {
1218 $term = get_term_by('name', sanitize_text_field($term), $taxonomy);
1219 if (empty($term) || !is_object($term)) {
1220 continue;
1221 }
1222
1223 $term = $term->term_id;
1224
1225 $args = array(
1226 'post_type' => $post_type,
1227 'posts_per_page' => 100,
1228 'fields' => 'ids',
1229 'tax_query' => array(
1230 array(
1231 'taxonomy' => $taxonomy,
1232 'field' => 'id',
1233 'terms' => $term,
1234 ),
1235 ),
1236 );
1237
1238 $post_ids = array();
1239 $page = 1;
1240 do {
1241 $args['paged'] = $page;
1242 $batch = get_posts($args);
1243 $post_ids = array_merge($post_ids, $batch);
1244 $page++;
1245 } while (count($batch) === $args['posts_per_page']);
1246
1247 foreach ($post_ids as $post_id) {
1248 if (!current_user_can('edit_post', $post_id)) {
1249 continue;
1250 }
1251 $remove = wp_remove_object_terms($post_id, $term, $taxonomy);
1252 if ($remove) {
1253 clean_object_term_cache($post_id, $taxonomy);
1254 clean_term_cache($term, $taxonomy);
1255 $counter++;
1256 }
1257 }
1258 }
1259
1260 if ($counter == 0) {
1261 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('This term is not associated with any %1$s.', 'simple-tags'), SimpleTags_Admin::$post_type_name), 'error taxopress-notice');
1262 } else {
1263 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Removed term(s) "%1$s" from %2$s %3$s', 'simple-tags'), $new, $counter, SimpleTags_Admin::$post_type_name), 'updated taxopress-notice');
1264 }
1265 } else { // Error
1266 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Error. No enough terms specified.', 'simple-tags'), $old), 'error taxopress-notice');
1267 }
1268
1269 return true;
1270 }
1271
1272 /**
1273 * Method for rename tags
1274 *
1275 * @param string $taxonomy
1276 * @param string $old
1277 * @param string $new
1278 *
1279 * @return boolean
1280 * @author WebFactory Ltd
1281 */
1282 public static function renameTerms($taxonomy = 'post_tag', $old = '', $new = '')
1283 {
1284 $taxonomy = sanitize_key($taxonomy);
1285 if (
1286 !taxopress_current_user_can_for_taxonomy(
1287 $taxonomy,
1288 ['edit_terms', 'delete_terms', 'assign_terms']
1289 )
1290 ) {
1291 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1292 return false;
1293 }
1294
1295 // Helper function to extract term name (ignoring slug in brackets)
1296 $extractTermName = function ($term) {
1297 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1298 };
1299
1300 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1301 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new term specified!', 'simple-tags'), 'error taxopress-notice');
1302
1303 return false;
1304 }
1305
1306 // String to array
1307 $old_terms = explode(',', $old);
1308 $new_terms = explode(',', $new);
1309
1310 // Clean up terms by removing slugs
1311 $old_terms = array_map($extractTermName, $old_terms);
1312 $new_terms = array_map($extractTermName, $new_terms);
1313
1314 // Remove empty element and trim
1315 $old_terms = array_filter($old_terms, '_delete_empty_element');
1316 $new_terms = array_filter($new_terms, '_delete_empty_element');
1317
1318 // If old/new tag are empty => exit !
1319 if (empty($old_terms) || empty($new_terms)) {
1320 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new/old valid term specified!', 'simple-tags'), 'error taxopress-notice');
1321
1322 return false;
1323 }
1324
1325 $counter = 0;
1326 if (count($old_terms) === count($new_terms)) { // Rename only
1327 foreach ((array) $old_terms as $i => $old_tag) {
1328 $new_name = sanitize_text_field($new_terms[ $i ]);
1329
1330 // Get term by name
1331 $term = get_term_by('name', sanitize_text_field($old_tag), $taxonomy);
1332 if (! $term) {
1333 continue;
1334 }
1335
1336 // Get objects from term ID
1337 $objects_id = get_objects_in_term($term->term_id, $taxonomy, array( 'fields' => 'all_with_object_id' ));
1338
1339 // Create the new term
1340 if (! $term_info = term_exists($new_name, $taxonomy)) {
1341 $term_info = wp_insert_term($new_name, $taxonomy);
1342 }
1343
1344 // If default category, update the ID for new term...
1345 if ('category' == $taxonomy && $term->term_id == get_option('default_category')) {
1346 update_option('default_category', $term_info['term_id']);
1347 clean_term_cache($term_info['term_id'], $taxonomy);
1348 }
1349
1350 // Delete old term
1351 wp_delete_term($term->term_id, $taxonomy);
1352
1353 // Set objects to new term ! (Append no replace)
1354 foreach ((array) $objects_id as $object_id) {
1355 if (!current_user_can('edit_post', $object_id)) {
1356 continue;
1357 }
1358 wp_set_object_terms($object_id, $new_name, $taxonomy, true);
1359 }
1360
1361 // Clean cache
1362 clean_object_term_cache($objects_id, $taxonomy);
1363 clean_term_cache($term->term_id, $taxonomy);
1364
1365 // Increment
1366 $counter++;
1367 }
1368
1369 if ($counter == 0) {
1370 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term renamed.', 'simple-tags'), 'updated taxopress-notice');
1371 } else {
1372 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Renamed term(s) "%1$s" to "%2$s"', 'simple-tags'), rtrim($old, ','), rtrim($new, ',')), 'updated taxopress-notice');
1373 }
1374 } else { // Error
1375 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Error. No enough terms for rename.', 'simple-tags'), $old), 'error taxopress-notice');
1376 }
1377
1378 return true;
1379 }
1380
1381 /**
1382 * Method for delete a list of terms
1383 *
1384 * @param string $taxonomy
1385 * @param string $delete
1386 *
1387 * @return boolean
1388 * @author WebFactory Ltd
1389 */
1390 public static function deleteTermsByTermList($taxonomy = 'post_tag', $delete = '')
1391 {
1392 $taxonomy = sanitize_key($taxonomy);
1393 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'delete_terms')) {
1394 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1395 return false;
1396 }
1397
1398 if (trim(str_replace(',', '', stripslashes($delete))) == '') {
1399 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term specified!', 'simple-tags'), 'error taxopress-notice');
1400
1401 return false;
1402 }
1403
1404 // In array + filter
1405 $delete_terms = explode(',', $delete);
1406 $delete_terms = array_filter($delete_terms, '_delete_empty_element');
1407
1408 // Delete tags
1409 $counter = 0;
1410 foreach ((array) $delete_terms as $term) {
1411 $term = get_term_by('name', sanitize_text_field($term), $taxonomy);
1412 $term_id = (int) $term->term_id;
1413
1414 if ($term_id != 0) {
1415 wp_delete_term($term_id, $taxonomy);
1416 clean_term_cache($term_id, $taxonomy);
1417 $counter++;
1418 }
1419 }
1420
1421 if ($counter == 0) {
1422 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term deleted.', 'simple-tags'), 'updated taxopress-notice');
1423 } else {
1424 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('%1s term(s) deleted.', 'simple-tags'), $counter), 'updated taxopress-notice');
1425 }
1426
1427 return true;
1428 }
1429
1430 /**
1431 * Method for add terms for all or specified posts
1432 *
1433 * @param string $taxonomy
1434 * @param string $match
1435 * @param string $new
1436 *
1437 * @return boolean
1438 * @author WebFactory Ltd
1439 */
1440 public static function addMatchTerms($taxonomy = 'post_tag', $match = '', $new = '')
1441 {
1442 // Helper function to extract term name (ignoring slug in brackets)
1443 $extractTermName = function ($term) {
1444 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1445 };
1446
1447 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1448 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new term(s) specified!', 'simple-tags'), 'error taxopress-notice');
1449
1450 return false;
1451 }
1452
1453 $match_terms = explode(',', $match);
1454 $new_terms = explode(',', $new);
1455
1456 // Clean up terms by removing slugs
1457 $match_terms = array_map($extractTermName, $match_terms);
1458 $new_terms = array_map($extractTermName, $new_terms);
1459
1460 $match_terms = array_filter($match_terms, '_delete_empty_element');
1461 $new_terms = array_filter($new_terms, '_delete_empty_element');
1462
1463 $counter = 0;
1464 if (! empty($match_terms)) { // Match and add
1465 // Get terms ID from old match names
1466 $terms_id = array();
1467 foreach ((array) $match_terms as $match_term) {
1468 $term = get_term_by('name', sanitize_text_field($match_term), $taxonomy);
1469 $terms_id[] = (int) $term->term_id;
1470 }
1471
1472 // Get object ID with terms ID
1473 $objects_id = get_objects_in_term($terms_id, $taxonomy, array( 'fields' => 'all_with_object_id' ));
1474
1475 // Add new tags for specified post
1476 foreach ((array) $objects_id as $object_id) {
1477 wp_set_object_terms($object_id, $new_terms, $taxonomy, true); // Append terms
1478 $counter++;
1479 }
1480
1481 // Clean cache
1482 clean_object_term_cache($objects_id, $taxonomy);
1483 clean_term_cache($terms_id, $taxonomy);
1484 } else { // Add for all posts
1485 // Get all posts ID
1486 global $wpdb;
1487 $post_type = sanitize_key(SimpleTags_Admin::$post_type);
1488 if (!post_type_exists($post_type)) {
1489 add_settings_error(__CLASS__, __CLASS__, esc_html__('Invalid post type.', 'simple-tags'), 'error taxopress-notice');
1490 return false;
1491 }
1492 SimpleTags_Admin::$post_type = $post_type;
1493 $objects_id = $wpdb->get_col(
1494 $wpdb->prepare(
1495 "SELECT ID FROM {$wpdb->posts} WHERE (post_status = 'publish' OR post_status = 'inherit') AND post_type = %s",
1496 $post_type
1497 )
1498 );
1499
1500 // Add new tags for all posts
1501 foreach ((array) $objects_id as $object_id) {
1502 wp_set_object_terms($object_id, $new_terms, $taxonomy, true); // Append terms
1503 clean_object_term_cache($object_id, $taxonomy);
1504 clean_term_cache($new_terms, $taxonomy);
1505 $counter++;
1506 }
1507
1508 // Clean cache
1509 clean_object_term_cache($objects_id, $taxonomy);
1510 }
1511
1512 if ($counter == 0) {
1513 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term added.', 'simple-tags'), 'updated taxopress-notice');
1514 } else {
1515 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Term(s) added to %1s %2s.', 'simple-tags'), $counter, SimpleTags_Admin::$post_type_name), 'updated taxopress-notice');
1516 }
1517
1518 return true;
1519 }
1520
1521 /**
1522 * Delete terms when counter if inferior to a specific number
1523 *
1524 * @param string $taxonomy
1525 * @param integer $number
1526 *
1527 * @return boolean
1528 * @author WebFactory Ltd
1529 */
1530 public static function removeRarelyUsed($taxonomy = 'post_tag', $number = 0)
1531 {
1532 global $wpdb;
1533
1534 if ((int) $number > 100) {
1535 wp_die('Tcheater ?');
1536 }
1537
1538 // Get terms with counter inferior to...
1539 $terms_id = $wpdb->get_col($wpdb->prepare("SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s AND count < %d", $taxonomy, (int) $number));
1540
1541 // Delete terms
1542 $counter = 0;
1543 foreach ((array) $terms_id as $term_id) {
1544 if ($term_id != 0) {
1545 wp_delete_term($term_id, $taxonomy);
1546 clean_term_cache($term_id, $taxonomy);
1547 $counter++;
1548 }
1549 }
1550
1551 if ($counter == 0) {
1552 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term deleted.', 'simple-tags'), 'updated taxopress-notice');
1553 } else {
1554 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('%1s term(s) deleted.', 'simple-tags'), $counter), 'updated taxopress-notice');
1555 }
1556
1557 return true;
1558 }
1559
1560 public function handle_taxopress_check_delete_terms_ajax()
1561 {
1562
1563 if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), 'st-admin-js')) {
1564 wp_send_json_error(array('message' => __('Nonce verification failed.', 'simple-tags')));
1565 wp_die();
1566 }
1567
1568 global $wpdb;
1569
1570 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : 'post_tag';
1571 $number = isset($_POST['number']) ? intval($_POST['number']) : 0;
1572
1573 $taxonomy_object = get_taxonomy($taxonomy);
1574 if (
1575 !current_user_can('simple_tags')
1576 || !$taxonomy_object
1577 || empty($taxonomy_object->cap->manage_terms)
1578 || !current_user_can($taxonomy_object->cap->manage_terms)
1579 ) {
1580 wp_send_json_error(array('message' => __('Permission denied.', 'simple-tags')), 403);
1581 }
1582
1583 if ((int) $number > 100) {
1584 wp_die('Tcheater ?');
1585 }
1586
1587 if ($number > 0) {
1588 $terms = $wpdb->get_col($wpdb->prepare(
1589 "
1590 SELECT term_id FROM $wpdb->term_taxonomy
1591 WHERE taxonomy = %s AND count < %d",
1592 $taxonomy,
1593 (int) $number
1594 ));
1595
1596 $term_count = count($terms);
1597
1598 if ($term_count > 0) {
1599 wp_send_json_success(array('message' => sprintf(__('%d terms will be deleted.', 'simple-tags'), $term_count)));
1600 } else {
1601 wp_send_json_error(array('message' => __('No terms will be deleted.', 'simple-tags')));
1602 }
1603 } else {
1604 wp_send_json_error(array('message' => __('Invalid number specified.', 'simple-tags')));
1605 }
1606
1607 wp_die();
1608 }
1609
1610 /**
1611 * Method for removing terms from all or specified posts
1612 *
1613 * @param string $taxonomy
1614 * @param string $match
1615 * @param string $remove
1616 *
1617 * @return boolean
1618 * @author WebFactory Ltd
1619 */
1620 public static function removeMatchTerms($taxonomy = 'post_tag', $match = '', $remove = '')
1621 {
1622 // Helper function to extract term name (ignoring slug in brackets)
1623 $extractTermName = function ($term) {
1624 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1625 };
1626
1627 if (trim(str_replace(',', '', stripslashes($remove))) == '') {
1628 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term(s) specified for removal!', 'simple-tags'), 'error taxopress-notice');
1629 return false;
1630 }
1631
1632 $match_terms = explode(',', $match);
1633 $remove_terms = explode(',', $remove);
1634
1635 // Clean up terms by removing slugs
1636 $match_terms = array_map($extractTermName, $match_terms);
1637 $remove_terms = array_map($extractTermName, $remove_terms);
1638
1639 $match_terms = array_filter($match_terms, '_delete_empty_element');
1640 $remove_terms = array_filter($remove_terms, '_delete_empty_element');
1641
1642 // Arrays to track if terms entered is valid
1643 $valid_remove_terms = array();
1644 $invalid_remove_terms = array();
1645
1646 foreach ((array) $remove_terms as $remove_term) {
1647 $term = get_term_by('name', sanitize_text_field($remove_term), $taxonomy);
1648 if ($term) {
1649 $valid_remove_terms[] = $remove_term; // Add to valid list if the term exists
1650 } else {
1651 $invalid_remove_terms[] = $remove_term; // Collect invalid remove terms
1652 }
1653 }
1654
1655 if (empty($valid_remove_terms)) {
1656 add_settings_error(__CLASS__, __CLASS__, esc_html__('Term(s) does not exist.', 'simple-tags'), 'error taxopress-notice');
1657 return false;
1658 }
1659
1660 $counter = 0;
1661 if (!empty($match_terms)) {
1662 // Get terms ID from match terms
1663 $terms_id = array();
1664 foreach ((array) $match_terms as $match_term) {
1665 $term = get_term_by('name', sanitize_text_field($match_term), $taxonomy);
1666 if ($term) {
1667 $terms_id[] = (int) $term->term_id;
1668 }
1669 }
1670
1671 // Get object ID with terms ID
1672 $objects_id = get_objects_in_term($terms_id, $taxonomy, array('fields' => 'all_with_object_id'));
1673
1674 // Remove specified terms from matched posts
1675 foreach ((array) $objects_id as $object_id) {
1676 wp_remove_object_terms($object_id, $valid_remove_terms, $taxonomy);
1677 $counter++;
1678 }
1679
1680 clean_object_term_cache($objects_id, $taxonomy);
1681 clean_term_cache($terms_id, $taxonomy);
1682 } else {
1683 // Get all posts if no match terms were provided
1684 global $wpdb;
1685 $post_type = sanitize_key(SimpleTags_Admin::$post_type);
1686 if (!post_type_exists($post_type)) {
1687 add_settings_error(__CLASS__, __CLASS__, esc_html__('Invalid post type.', 'simple-tags'), 'error taxopress-notice');
1688 return false;
1689 }
1690 SimpleTags_Admin::$post_type = $post_type;
1691 $objects_id = $wpdb->get_col(
1692 $wpdb->prepare(
1693 "SELECT ID FROM {$wpdb->posts} WHERE (post_status = 'publish' OR post_status = 'inherit') AND post_type = %s",
1694 $post_type
1695 )
1696 );
1697
1698 // Remove valid terms for all posts
1699 foreach ((array) $objects_id as $object_id) {
1700 wp_remove_object_terms($object_id, $valid_remove_terms, $taxonomy);
1701 clean_object_term_cache($object_id, $taxonomy);
1702 clean_term_cache($valid_remove_terms, $taxonomy);
1703 $counter++;
1704 }
1705
1706 clean_object_term_cache($objects_id, $taxonomy);
1707 }
1708
1709 if ($counter == 0) {
1710 add_settings_error(__CLASS__, __CLASS__, esc_html__('No matching term found.', 'simple-tags'), 'updated taxopress-notice');
1711 } else {
1712 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Term(s) removed from %1s %2s.', 'simple-tags'), $counter, SimpleTags_Admin::$post_type_name), 'updated taxopress-notice');
1713 }
1714
1715 return true;
1716 }
1717
1718 public static function handle_taxopress_autocomplete_terms()
1719 {
1720 if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), 'st-admin-js')) {
1721 wp_send_json_error(['message' => __('Nonce verification failed.', 'simple-tags')]);
1722 wp_die();
1723 }
1724
1725 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : 'post_tag';
1726 $term = isset($_POST['term']) ? sanitize_text_field(wp_unslash($_POST['term'])) : '';
1727
1728 $taxonomy_object = get_taxonomy($taxonomy);
1729 if (
1730 !current_user_can('simple_tags')
1731 || !$taxonomy_object
1732 || empty($taxonomy_object->cap->manage_terms)
1733 || !current_user_can($taxonomy_object->cap->manage_terms)
1734 ) {
1735 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1736 }
1737
1738 $terms = get_terms([
1739 'taxonomy' => $taxonomy,
1740 'name__like' => $term,
1741 'hide_empty' => false,
1742 'number' => (int) apply_filters('taxopress_autocomplete_terms_limit', 50),
1743 'update_term_meta_cache' => false,
1744 ]);
1745
1746 $results = [];
1747 foreach ($terms as $term) {
1748 $results[] = array(
1749 'name' => html_entity_decode(
1750 $term->name,
1751 ENT_QUOTES,
1752 get_bloginfo('charset')
1753 ),
1754 'slug' => $term->slug,
1755 );
1756 }
1757
1758 wp_send_json($results);
1759 wp_die();
1760 }
1761
1762
1763 /**
1764 * Method for edit one or more terms slug
1765 *
1766 * @param string $taxonomy
1767 * @param string $names
1768 * @param string $slugs
1769 *
1770 * @return boolean
1771 * @author WebFactory Ltd
1772 */
1773 /*
1774 public static function editTermSlug( $taxonomy = 'post_tag', $names = '', $slugs = '') {
1775 if ( trim( str_replace(',', '', stripslashes($slugs)) ) == '' ) {
1776 add_settings_error( __CLASS__, __CLASS__, esc_html__('No new slug(s) specified!', 'simple-tags'), 'error' );
1777 return false;
1778 }
1779
1780 $match_names = explode(',', $names);
1781 $new_slugs = explode(',', $slugs);
1782
1783 $match_names = array_filter($match_names, '_delete_empty_element');
1784 $new_slugs = array_filter($new_slugs, '_delete_empty_element');
1785
1786 if ( count($match_names) != count($new_slugs) ) {
1787 add_settings_error( __CLASS__, __CLASS__, esc_html__('Terms number and slugs number isn\'t the same!', 'simple-tags'), 'error' );
1788 return false;
1789 } else {
1790 $counter = 0;
1791 foreach ( (array) $match_names as $i => $match_name ) {
1792 // Sanitize slug + Escape
1793 $new_slug = sanitize_title($new_slugs[$i]);
1794
1795 // Get term by name
1796 $term = get_term_by('name', $match_name, $taxonomy);
1797 if ( !$term ) {
1798 continue;
1799 }
1800
1801 // Increment
1802 $counter++;
1803
1804 // Update term
1805 wp_update_term($term->term_id, $taxonomy, array('slug' => $new_slug));
1806
1807 // Clean cache
1808 clean_term_cache($term->term_id, $taxonomy);
1809 }
1810 }
1811
1812 if ( $counter == 0 ) {
1813 add_settings_error( __CLASS__, __CLASS__, esc_html__('No slug edited.', 'simple-tags'), 'updated' );
1814 } else {
1815 add_settings_error( __CLASS__, __CLASS__, sprintf(esc_html__('%s slug(s) edited.', 'simple-tags'), $counter), 'updated' );
1816 }
1817
1818 return true;
1819 }
1820 */
1821
1822
1823 /** Singleton instance */
1824 public static function get_instance()
1825 {
1826 if (! isset(self::$instance)) {
1827 self::$instance = new self();
1828 }
1829
1830 return self::$instance;
1831 }
1832 }
1833 ?>
1834