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

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

1,818 lines 83.0 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 // Ensure the new term exists or create it
658 $new_term = get_term_by('name', $new_tag, $taxonomy);
659 if (!$new_term) {
660 $new_term_info = wp_insert_term($new_tag, $taxonomy);
661 if (is_wp_error($new_term_info)) {
662 add_settings_error(__CLASS__, __CLASS__, esc_html__('Failed to create the new term.', 'simple-tags'), 'error taxopress-notice');
663 return false;
664 }
665 $new_term_id = $new_term_info['term_id'];
666 $new_term_slug = isset($new_term_info['slug']) ? $new_term_info['slug'] : sanitize_title($new_tag);
667 } else {
668 $new_term_id = $new_term->term_id;
669 $new_term_slug = $new_term->slug;
670 }
671
672 // Get terms ID from old terms names
673 $terms_id = array();
674 $found_terms = array();
675 foreach ((array) $old_terms as $old_tag) {
676 $term = get_term_by('name', addslashes(sanitize_text_field($old_tag)), $taxonomy);
677 if ($term) {
678 $terms_id[] = (int) $term->term_id;
679 $found_terms[] = $term->name;
680 }
681 }
682
683 if (empty($terms_id)) {
684 add_settings_error(__CLASS__, __CLASS__, esc_html__('No matching terms found to merge.', 'simple-tags'), 'error taxopress-notice');
685 return false;
686 }
687
688 // Get objects from terms ID
689 $objects_id = get_objects_in_term($terms_id, $taxonomy, ['fields' => 'ids']);
690
691 // Use a set to track unique post IDs
692 $unique_objects = [];
693
694 // Assign the new term to all posts associated with the old terms
695 foreach ((array) $objects_id as $object_id) {
696 if (!current_user_can('edit_post', $object_id)) {
697 continue;
698 }
699 // Check if the object already has the term assigned
700 $current_terms = wp_get_object_terms($object_id, $taxonomy, ['fields' => 'ids']);
701 if (!in_array($new_term_id, $current_terms)) {
702 wp_set_object_terms($object_id, $new_term_slug, $taxonomy, true);
703 }
704 $unique_objects[$object_id] = true; // Add to unique set
705 }
706
707 // Count unique posts
708 $counter = count($unique_objects);
709
710 // Delete old terms
711 foreach ((array) $terms_id as $term_id) {
712 wp_delete_term($term_id, $taxonomy);
713 }
714
715 // Test if term is also a category
716 /*
717 if ( is_term($new_tag, 'category') ) {
718 // Edit the slug to use the new term
719 self::editTermSlug( $new_tag, sanitize_title($new_tag) );
720 }
721 */
722
723 // Clean cache
724 clean_object_term_cache($objects_id, $taxonomy);
725 clean_term_cache($terms_id, $taxonomy);
726
727
728 if (empty($found_terms)) {
729 add_settings_error(__CLASS__, __CLASS__, esc_html__('No terms were merged (terms may not exist).', 'simple-tags'), 'error taxopress-notice');
730 } else {
731 $message = sprintf(
732 esc_html__('Successfully merged %1$d term(s) "%2$s" to "%3$s". %4$s posts updated.', 'simple-tags'),
733 count($found_terms),
734 implode(', ', $found_terms),
735 rtrim($new, ','),
736 $counter
737 );
738 add_settings_error(__CLASS__, __CLASS__, $message, 'updated taxopress-notice');
739 }
740 } else { // Error
741 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');
742 return false;
743 }
744
745 return [
746 'success' => true,
747 'merged_into' => $new_tag,
748 'posts_updated' => $counter,
749 'post_ids' => array_keys($unique_objects),
750 'terms_merged' => count($terms_id) + 1,
751 ];
752 }
753 }
754
755 /**
756 * Find terms with same names but different slugs
757 */
758 public static function getSameNameMergeSuggestions($taxonomy = 'post_tag')
759 {
760 global $wpdb;
761
762 $query = $wpdb->prepare("
763 SELECT t.name, COUNT(*) as count, GROUP_CONCAT(CONCAT(t.name, ' (', t.slug, ')') SEPARATOR ', ') as terms
764 FROM {$wpdb->terms} t
765 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
766 WHERE tt.taxonomy = %s
767 GROUP BY t.name
768 HAVING COUNT(*) > 1
769 ORDER BY count DESC, t.name ASC
770 ", $taxonomy);
771
772 $results = $wpdb->get_results($query);
773
774 $show_slug = (int) SimpleTags_Plugin::get_option_value('enable_merge_terms_slug');
775
776 if (!$show_slug) {
777 foreach ($results as &$result) {
778 $result->terms = preg_replace('/\s*\([^)]+\)/', '', $result->terms);
779 }
780 }
781
782 return $results;
783 }
784
785 /**
786 * Find terms that might be candidates for merging based on similarity
787 */
788 public static function getDifferentNameMergeSuggestions($taxonomy = 'post_tag', $limit = 50)
789 {
790 global $wpdb;
791
792 $limit = min(max((int) $limit, 1), 50);
793
794 $terms = get_terms([
795 'taxonomy' => $taxonomy,
796 'hide_empty' => false,
797 'number' => $limit * 2,
798 'update_term_meta_cache' => false,
799 ]);
800
801 $show_slug = (int) SimpleTags_Plugin::get_option_value('enable_merge_terms_slug');
802
803 $suggestions = [];
804
805 foreach ($terms as $i => $term1) {
806 foreach ($terms as $j => $term2) {
807 if ($i >= $j) {
808 continue;
809 }
810
811 $similarity_score = 0;
812 $reasons = [];
813
814 // 1. Levenshtein distance (for typos)
815 $term1_name = strtolower($term1->name);
816 $term2_name = strtolower($term2->name);
817 if (abs(strlen($term1_name) - strlen($term2_name)) <= 2 && max(strlen($term1_name), strlen($term2_name)) <= 100) {
818 $levenshtein = levenshtein($term1_name, $term2_name);
819 if ($levenshtein <= 2 && $levenshtein > 0) {
820 $similarity_score += 40;
821 $reasons[] = esc_html__('Similar spelling', 'simple-tags');
822 }
823 }
824
825 // 2. Soundex (phonetic similarity)
826 if (soundex($term1->name) === soundex($term2->name)) {
827 $similarity_score += 30;
828 $reasons[] = esc_html__('Sounds similar', 'simple-tags');
829 }
830
831 // 3. Common words/partial matches
832 $words1 = explode(' ', strtolower($term1->name));
833 $words2 = explode(' ', strtolower($term2->name));
834 $common_words = array_intersect($words1, $words2);
835 if (!empty($common_words)) {
836 $similarity_score += count($common_words) * 15;
837 $reasons[] = sprintf(esc_html__('Shared words: %s', 'simple-tags'), implode(', ', $common_words));
838 }
839
840 // 4. Prefix/suffix similarity
841 if (strpos($term1->name, $term2->name) !== false || strpos($term2->name, $term1->name) !== false) {
842 $similarity_score += 25;
843 $reasons[] = esc_html__('One contains the other', 'simple-tags');
844 }
845
846 if ($similarity_score >= 30) {
847 $suggested_name = '';
848
849 // Get clean term names for comparison
850 $term1_clean = strtolower(trim($term1->name));
851 $term2_clean = strtolower(trim($term2->name));
852
853 if (!empty($common_words)) {
854 $suggested_name = ucfirst(implode(' ', $common_words));
855 } else {
856 // Use the shorter term as base
857 $suggested_name = strlen($term1->name) <= strlen($term2->name) ? $term1->name : $term2->name;
858 }
859
860 // let's make sure suggested name is different from both original terms
861 $suggested_clean = strtolower(trim($suggested_name));
862 if ($suggested_clean === $term1_clean || $suggested_clean === $term2_clean) {
863 if (!empty($common_words)) {
864 $suggested_name = ucfirst($common_words[0]);
865 } else {
866 $popular_term = ($term1->count >= $term2->count) ? $term1 : $term2;
867 $suggested_name = $popular_term->name;
868 }
869
870 $suggested_clean = strtolower(trim($suggested_name));
871 }
872
873 $term1_display = $show_slug ? $term1->name . ' (' . $term1->slug . ')' : $term1->name;
874 $term2_display = $show_slug ? $term2->name . ' (' . $term2->slug . ')' : $term2->name;
875
876 $suggestions[] = [
877 'term1' => $term1_display,
878 'term2' => $term2_display,
879 'score' => $similarity_score,
880 'reasons' => implode(', ', $reasons),
881 'suggested_name' => $suggested_name
882 ];
883 }
884 }
885 }
886
887 usort($suggestions, function ($a, $b) {
888 return $b['score'] - $a['score'];
889 });
890
891 return array_slice($suggestions, 0, $limit);
892 }
893
894 /**
895 * AJAX handler for merge suggestions
896 */
897 public function handle_taxopress_merge_suggestions()
898 {
899 if (!current_user_can('simple_tags')) {
900 wp_send_json_error('Permission denied');
901 }
902
903 if (!wp_verify_nonce(wp_unslash($_POST['nonce']), 'simpletags_admin')) {
904 wp_send_json_error('Invalid nonce');
905 }
906
907 $taxonomy = get_option('merge-terms_taxo', 'post_tag');
908 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'manage_terms')) {
909 wp_send_json_error('Permission denied', 403);
910 }
911 $merge_type = sanitize_text_field(wp_unslash($_POST['merge_type']));
912
913 if ($merge_type === 'same_name') {
914 $suggestions = self::getSameNameMergeSuggestions($taxonomy);
915 wp_send_json_success([
916 'type' => 'same_name',
917 'suggestions' => $suggestions
918 ]);
919 } else {
920 $suggestions = self::getDifferentNameMergeSuggestions($taxonomy);
921 wp_send_json_success([
922 'type' => 'different_name',
923 'suggestions' => $suggestions
924 ]);
925 }
926 }
927
928 private static function get_merge_preflight_term_ids($taxonomy, $old_terms_input, $merge_type)
929 {
930 $extractTermName = function ($term) {
931 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
932 };
933
934 $term_ids = [];
935
936 foreach ($old_terms_input as $term_name) {
937 $term_name_clean = sanitize_text_field($extractTermName($term_name));
938
939 if (empty($term_name_clean)) {
940 continue;
941 }
942
943 if ($merge_type === 'same_name') {
944 $matching_terms = get_terms([
945 'taxonomy' => $taxonomy,
946 'name' => $term_name_clean,
947 'hide_empty' => false,
948 'fields' => 'ids',
949 'update_term_meta_cache' => false,
950 ]);
951
952 if (!is_wp_error($matching_terms) && !empty($matching_terms)) {
953 foreach ($matching_terms as $term_id) {
954 $term_ids[] = (int) $term_id;
955 }
956
957 continue;
958 }
959 }
960
961 $term = get_term_by('name', $term_name_clean, $taxonomy);
962 if (!$term || is_wp_error($term)) {
963 $term = get_term_by('slug', sanitize_title($term_name_clean), $taxonomy);
964 }
965
966 if ($term && !is_wp_error($term)) {
967 $term_ids[] = (int) $term->term_id;
968 }
969 }
970
971 return array_values(array_unique(array_filter($term_ids)));
972 }
973
974 private static function count_objects_for_merge_terms($taxonomy, $term_ids)
975 {
976 global $wpdb;
977
978 if (empty($term_ids)) {
979 return 0;
980 }
981
982 $term_ids = array_values(array_unique(array_map('intval', $term_ids)));
983 $cache_key = 'merge_preflight_' . md5($taxonomy . ':' . implode(',', $term_ids));
984 $cached_count = wp_cache_get($cache_key, 'taxopress_terms');
985
986 if ($cached_count !== false) {
987 return (int) $cached_count;
988 }
989
990 $term_id_placeholders = implode(',', array_fill(0, count($term_ids), '%d'));
991 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
992 $term_taxonomy_query = $wpdb->prepare(
993 "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s AND term_id IN ({$term_id_placeholders})",
994 array_merge([$taxonomy], $term_ids)
995 );
996
997 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
998 $term_taxonomy_ids = $wpdb->get_col($term_taxonomy_query);
999
1000 if (empty($term_taxonomy_ids)) {
1001 wp_cache_set($cache_key, 0, 'taxopress_terms', MINUTE_IN_SECONDS);
1002 return 0;
1003 }
1004
1005 $term_taxonomy_ids = array_values(array_unique(array_map('intval', $term_taxonomy_ids)));
1006 $term_taxonomy_id_placeholders = implode(',', array_fill(0, count($term_taxonomy_ids), '%d'));
1007 $object_count_query = $wpdb->prepare(
1008 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1009 "SELECT COUNT(DISTINCT object_id) FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ({$term_taxonomy_id_placeholders})",
1010 $term_taxonomy_ids
1011 );
1012
1013 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1014 $object_count = (int) $wpdb->get_var($object_count_query);
1015
1016 wp_cache_set($cache_key, $object_count, 'taxopress_terms', MINUTE_IN_SECONDS);
1017
1018 return $object_count;
1019 }
1020
1021 public static function taxopress_merge_terms_preflight()
1022 {
1023 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
1024 if (empty($nonce) || !wp_verify_nonce($nonce, 'st-admin-js')) {
1025 wp_send_json_error(['message' => __('Security check failed.', 'simple-tags')], 403);
1026 wp_die();
1027 }
1028
1029 if (!current_user_can('simple_tags')) {
1030 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1031 wp_die();
1032 }
1033
1034 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : '';
1035 if (empty($taxonomy) || !taxonomy_exists($taxonomy)) {
1036 wp_send_json_error(['message' => __('Invalid taxonomy.', 'simple-tags')], 400);
1037 wp_die();
1038 }
1039
1040 if (
1041 !taxopress_current_user_can_for_taxonomy(
1042 $taxonomy,
1043 ['edit_terms', 'delete_terms', 'assign_terms']
1044 )
1045 ) {
1046 wp_send_json_error(['message' => __('Permission denied for this taxonomy.', 'simple-tags')], 403);
1047 wp_die();
1048 }
1049
1050 $merge_type = isset($_POST['merge_type']) ? sanitize_text_field(wp_unslash($_POST['merge_type'])) : 'different_name';
1051 $old_terms_input = isset($_POST['old_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_POST['old_terms'])) : [];
1052
1053 $term_ids = self::get_merge_preflight_term_ids($taxonomy, $old_terms_input, $merge_type);
1054 $affected_posts_count = self::count_objects_for_merge_terms($taxonomy, $term_ids);
1055 $workload = count($term_ids) * $affected_posts_count;
1056 $ajax_workload_threshold = (int) apply_filters('taxopress_merge_terms_ajax_workload_threshold', 20);
1057
1058 wp_send_json_success([
1059 'terms_count' => count($term_ids),
1060 'affected_posts_count' => $affected_posts_count,
1061 'workload' => $workload,
1062 'use_ajax' => $workload > $ajax_workload_threshold,
1063 ]);
1064 }
1065
1066 public static function taxopress_merge_terms_batch()
1067 {
1068 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
1069 if (empty($nonce) || !wp_verify_nonce($nonce, 'st-admin-js')) {
1070 wp_send_json_error(['message' => __('Security check failed.', 'simple-tags')], 403);
1071 wp_die();
1072 }
1073
1074 if (!current_user_can('simple_tags')) {
1075 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1076 wp_die();
1077 }
1078
1079 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : '';
1080 $new_term = isset($_POST['new_term']) ? sanitize_text_field(wp_unslash($_POST['new_term'])) : '';
1081 $merge_type = isset($_POST['merge_type']) ? sanitize_text_field(wp_unslash($_POST['merge_type'])) : 'different_name';
1082 $old_terms_input = isset($_POST['old_terms']) ? array_map('sanitize_text_field', (array) wp_unslash($_POST['old_terms'])) : [];
1083
1084 if (
1085 !taxopress_current_user_can_for_taxonomy(
1086 $taxonomy,
1087 ['edit_terms', 'delete_terms', 'assign_terms']
1088 )
1089 ) {
1090 wp_send_json_error(['message' => __('Permission denied for this taxonomy.', 'simple-tags')], 403);
1091 wp_die();
1092 }
1093
1094 $extractTermName = function ($term) {
1095 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1096 };
1097
1098 $old_terms = [];
1099
1100 foreach ($old_terms_input as $term_name) {
1101 $term_name_clean = $extractTermName($term_name);
1102 $term = get_term_by('name', $term_name_clean, $taxonomy);
1103 if (!$term || is_wp_error($term)) {
1104 $term = get_term_by('slug', sanitize_title($term_name_clean), $taxonomy);
1105 }
1106 if ($term && !is_wp_error($term)) {
1107 $old_terms[] = $term->name;
1108 }
1109 }
1110
1111 $old_terms = array_values(array_unique($old_terms));
1112
1113 if (empty($old_terms)) {
1114 wp_send_json_error([
1115 'message' => __('Please enter a valid term. Merge cancelled.', 'simple-tags')
1116 ]);
1117 wp_die();
1118 }
1119
1120 if ($merge_type === 'different_name' && !empty($new_term)) {
1121 $old_terms = array_values(array_filter($old_terms, function ($term_name) use ($new_term) {
1122 return strtolower(trim($term_name)) !== strtolower(trim($new_term));
1123 }));
1124 }
1125
1126
1127 // Execute the merge
1128 $result = SimpleTags_Admin_Manage::mergeTerms($taxonomy, implode(',', $old_terms), $new_term, $merge_type);
1129
1130 if ($result === true || (is_array($result) && !empty($result['success']))) {
1131 $response = ['message' => __('Merge successful', 'simple-tags')];
1132
1133 if (is_array($result)) {
1134 $response = array_merge($response, $result);
1135 }
1136
1137 wp_send_json_success($response);
1138 } else {
1139 global $wp_settings_errors;
1140 $errors = [];
1141
1142 if (!empty($wp_settings_errors)) {
1143 foreach ($wp_settings_errors as $error) {
1144 if (!empty($error['message'])) {
1145 $errors[] = $error['message'];
1146 }
1147 }
1148 }
1149
1150 if (is_wp_error($result)) {
1151 $errors[] = $result->get_error_message();
1152 } elseif (is_array($result) && isset($result['message'])) {
1153 $errors[] = $result['message'];
1154 }
1155
1156 wp_send_json_error(['message' => implode(' ', $errors)]);
1157 }
1158
1159 wp_die();
1160 }
1161
1162 /**
1163 * Method for remove tags
1164 *
1165 * @param string $taxonomy
1166 * @param string $post_type
1167 * @param string $tag
1168 *
1169 * @return boolean
1170 * @author WebFactory Ltd
1171 */
1172 public static function removeTerms($taxonomy = 'post_tag', $post_type = 'posts', $new = '')
1173 {
1174 $taxonomy = sanitize_key($taxonomy);
1175 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'assign_terms')) {
1176 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1177 return false;
1178 }
1179
1180 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1181 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term specified!', 'simple-tags'), 'error taxopress-notice');
1182
1183 return false;
1184 }
1185
1186 // String to array
1187 $new_terms = explode(',', $new);
1188
1189 // Remove empty element and trim
1190 $new_terms = array_filter($new_terms, '_delete_empty_element');
1191
1192 // If new tag are empty => exit !
1193 if (empty($new_terms)) {
1194 add_settings_error(__CLASS__, __CLASS__, esc_html__('No valid term specified!', 'simple-tags'), 'error taxopress-notice');
1195
1196 return false;
1197 }
1198
1199 $counter = 0;
1200 if (count($new_terms) > 0) {
1201 foreach ((array) $new_terms as $term) {
1202 $term = get_term_by('name', sanitize_text_field($term), $taxonomy);
1203 if (empty($term) || !is_object($term)) {
1204 continue;
1205 }
1206
1207 $term = $term->term_id;
1208
1209 $args = array(
1210 'post_type' => $post_type,
1211 'posts_per_page' => 100,
1212 'fields' => 'ids',
1213 'tax_query' => array(
1214 array(
1215 'taxonomy' => $taxonomy,
1216 'field' => 'id',
1217 'terms' => $term,
1218 ),
1219 ),
1220 );
1221
1222 $post_ids = array();
1223 $page = 1;
1224 do {
1225 $args['paged'] = $page;
1226 $batch = get_posts($args);
1227 $post_ids = array_merge($post_ids, $batch);
1228 $page++;
1229 } while (count($batch) === $args['posts_per_page']);
1230
1231 foreach ($post_ids as $post_id) {
1232 if (!current_user_can('edit_post', $post_id)) {
1233 continue;
1234 }
1235 $remove = wp_remove_object_terms($post_id, $term, $taxonomy);
1236 if ($remove) {
1237 clean_object_term_cache($post_id, $taxonomy);
1238 clean_term_cache($term, $taxonomy);
1239 $counter++;
1240 }
1241 }
1242 }
1243
1244 if ($counter == 0) {
1245 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');
1246 } else {
1247 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');
1248 }
1249 } else { // Error
1250 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Error. No enough terms specified.', 'simple-tags'), $old), 'error taxopress-notice');
1251 }
1252
1253 return true;
1254 }
1255
1256 /**
1257 * Method for rename tags
1258 *
1259 * @param string $taxonomy
1260 * @param string $old
1261 * @param string $new
1262 *
1263 * @return boolean
1264 * @author WebFactory Ltd
1265 */
1266 public static function renameTerms($taxonomy = 'post_tag', $old = '', $new = '')
1267 {
1268 $taxonomy = sanitize_key($taxonomy);
1269 if (
1270 !taxopress_current_user_can_for_taxonomy(
1271 $taxonomy,
1272 ['edit_terms', 'delete_terms', 'assign_terms']
1273 )
1274 ) {
1275 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1276 return false;
1277 }
1278
1279 // Helper function to extract term name (ignoring slug in brackets)
1280 $extractTermName = function ($term) {
1281 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1282 };
1283
1284 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1285 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new term specified!', 'simple-tags'), 'error taxopress-notice');
1286
1287 return false;
1288 }
1289
1290 // String to array
1291 $old_terms = explode(',', $old);
1292 $new_terms = explode(',', $new);
1293
1294 // Clean up terms by removing slugs
1295 $old_terms = array_map($extractTermName, $old_terms);
1296 $new_terms = array_map($extractTermName, $new_terms);
1297
1298 // Remove empty element and trim
1299 $old_terms = array_filter($old_terms, '_delete_empty_element');
1300 $new_terms = array_filter($new_terms, '_delete_empty_element');
1301
1302 // If old/new tag are empty => exit !
1303 if (empty($old_terms) || empty($new_terms)) {
1304 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new/old valid term specified!', 'simple-tags'), 'error taxopress-notice');
1305
1306 return false;
1307 }
1308
1309 $counter = 0;
1310 if (count($old_terms) === count($new_terms)) { // Rename only
1311 foreach ((array) $old_terms as $i => $old_tag) {
1312 $new_name = sanitize_text_field($new_terms[ $i ]);
1313
1314 // Get term by name
1315 $term = get_term_by('name', sanitize_text_field($old_tag), $taxonomy);
1316 if (! $term) {
1317 continue;
1318 }
1319
1320 // Get objects from term ID
1321 $objects_id = get_objects_in_term($term->term_id, $taxonomy, array( 'fields' => 'all_with_object_id' ));
1322
1323 // Create the new term
1324 if (! $term_info = term_exists($new_name, $taxonomy)) {
1325 $term_info = wp_insert_term($new_name, $taxonomy);
1326 }
1327
1328 // If default category, update the ID for new term...
1329 if ('category' == $taxonomy && $term->term_id == get_option('default_category')) {
1330 update_option('default_category', $term_info['term_id']);
1331 clean_term_cache($term_info['term_id'], $taxonomy);
1332 }
1333
1334 // Delete old term
1335 wp_delete_term($term->term_id, $taxonomy);
1336
1337 // Set objects to new term ! (Append no replace)
1338 foreach ((array) $objects_id as $object_id) {
1339 if (!current_user_can('edit_post', $object_id)) {
1340 continue;
1341 }
1342 wp_set_object_terms($object_id, $new_name, $taxonomy, true);
1343 }
1344
1345 // Clean cache
1346 clean_object_term_cache($objects_id, $taxonomy);
1347 clean_term_cache($term->term_id, $taxonomy);
1348
1349 // Increment
1350 $counter++;
1351 }
1352
1353 if ($counter == 0) {
1354 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term renamed.', 'simple-tags'), 'updated taxopress-notice');
1355 } else {
1356 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');
1357 }
1358 } else { // Error
1359 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('Error. No enough terms for rename.', 'simple-tags'), $old), 'error taxopress-notice');
1360 }
1361
1362 return true;
1363 }
1364
1365 /**
1366 * Method for delete a list of terms
1367 *
1368 * @param string $taxonomy
1369 * @param string $delete
1370 *
1371 * @return boolean
1372 * @author WebFactory Ltd
1373 */
1374 public static function deleteTermsByTermList($taxonomy = 'post_tag', $delete = '')
1375 {
1376 $taxonomy = sanitize_key($taxonomy);
1377 if (!taxopress_current_user_can_for_taxonomy($taxonomy, 'delete_terms')) {
1378 add_settings_error(__CLASS__, __CLASS__, esc_html__('Permission denied for this taxonomy.', 'simple-tags'), 'error taxopress-notice');
1379 return false;
1380 }
1381
1382 if (trim(str_replace(',', '', stripslashes($delete))) == '') {
1383 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term specified!', 'simple-tags'), 'error taxopress-notice');
1384
1385 return false;
1386 }
1387
1388 // In array + filter
1389 $delete_terms = explode(',', $delete);
1390 $delete_terms = array_filter($delete_terms, '_delete_empty_element');
1391
1392 // Delete tags
1393 $counter = 0;
1394 foreach ((array) $delete_terms as $term) {
1395 $term = get_term_by('name', sanitize_text_field($term), $taxonomy);
1396 $term_id = (int) $term->term_id;
1397
1398 if ($term_id != 0) {
1399 wp_delete_term($term_id, $taxonomy);
1400 clean_term_cache($term_id, $taxonomy);
1401 $counter++;
1402 }
1403 }
1404
1405 if ($counter == 0) {
1406 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term deleted.', 'simple-tags'), 'updated taxopress-notice');
1407 } else {
1408 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('%1s term(s) deleted.', 'simple-tags'), $counter), 'updated taxopress-notice');
1409 }
1410
1411 return true;
1412 }
1413
1414 /**
1415 * Method for add terms for all or specified posts
1416 *
1417 * @param string $taxonomy
1418 * @param string $match
1419 * @param string $new
1420 *
1421 * @return boolean
1422 * @author WebFactory Ltd
1423 */
1424 public static function addMatchTerms($taxonomy = 'post_tag', $match = '', $new = '')
1425 {
1426 // Helper function to extract term name (ignoring slug in brackets)
1427 $extractTermName = function ($term) {
1428 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1429 };
1430
1431 if (trim(str_replace(',', '', stripslashes($new))) == '') {
1432 add_settings_error(__CLASS__, __CLASS__, esc_html__('No new term(s) specified!', 'simple-tags'), 'error taxopress-notice');
1433
1434 return false;
1435 }
1436
1437 $match_terms = explode(',', $match);
1438 $new_terms = explode(',', $new);
1439
1440 // Clean up terms by removing slugs
1441 $match_terms = array_map($extractTermName, $match_terms);
1442 $new_terms = array_map($extractTermName, $new_terms);
1443
1444 $match_terms = array_filter($match_terms, '_delete_empty_element');
1445 $new_terms = array_filter($new_terms, '_delete_empty_element');
1446
1447 $counter = 0;
1448 if (! empty($match_terms)) { // Match and add
1449 // Get terms ID from old match names
1450 $terms_id = array();
1451 foreach ((array) $match_terms as $match_term) {
1452 $term = get_term_by('name', sanitize_text_field($match_term), $taxonomy);
1453 $terms_id[] = (int) $term->term_id;
1454 }
1455
1456 // Get object ID with terms ID
1457 $objects_id = get_objects_in_term($terms_id, $taxonomy, array( 'fields' => 'all_with_object_id' ));
1458
1459 // Add new tags for specified post
1460 foreach ((array) $objects_id as $object_id) {
1461 wp_set_object_terms($object_id, $new_terms, $taxonomy, true); // Append terms
1462 $counter++;
1463 }
1464
1465 // Clean cache
1466 clean_object_term_cache($objects_id, $taxonomy);
1467 clean_term_cache($terms_id, $taxonomy);
1468 } else { // Add for all posts
1469 // Get all posts ID
1470 global $wpdb;
1471 $post_type = sanitize_key(SimpleTags_Admin::$post_type);
1472 if (!post_type_exists($post_type)) {
1473 add_settings_error(__CLASS__, __CLASS__, esc_html__('Invalid post type.', 'simple-tags'), 'error taxopress-notice');
1474 return false;
1475 }
1476 SimpleTags_Admin::$post_type = $post_type;
1477 $objects_id = $wpdb->get_col(
1478 $wpdb->prepare(
1479 "SELECT ID FROM {$wpdb->posts} WHERE (post_status = 'publish' OR post_status = 'inherit') AND post_type = %s",
1480 $post_type
1481 )
1482 );
1483
1484 // Add new tags for all posts
1485 foreach ((array) $objects_id as $object_id) {
1486 wp_set_object_terms($object_id, $new_terms, $taxonomy, true); // Append terms
1487 clean_object_term_cache($object_id, $taxonomy);
1488 clean_term_cache($new_terms, $taxonomy);
1489 $counter++;
1490 }
1491
1492 // Clean cache
1493 clean_object_term_cache($objects_id, $taxonomy);
1494 }
1495
1496 if ($counter == 0) {
1497 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term added.', 'simple-tags'), 'updated taxopress-notice');
1498 } else {
1499 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');
1500 }
1501
1502 return true;
1503 }
1504
1505 /**
1506 * Delete terms when counter if inferior to a specific number
1507 *
1508 * @param string $taxonomy
1509 * @param integer $number
1510 *
1511 * @return boolean
1512 * @author WebFactory Ltd
1513 */
1514 public static function removeRarelyUsed($taxonomy = 'post_tag', $number = 0)
1515 {
1516 global $wpdb;
1517
1518 if ((int) $number > 100) {
1519 wp_die('Tcheater ?');
1520 }
1521
1522 // Get terms with counter inferior to...
1523 $terms_id = $wpdb->get_col($wpdb->prepare("SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s AND count < %d", $taxonomy, (int) $number));
1524
1525 // Delete terms
1526 $counter = 0;
1527 foreach ((array) $terms_id as $term_id) {
1528 if ($term_id != 0) {
1529 wp_delete_term($term_id, $taxonomy);
1530 clean_term_cache($term_id, $taxonomy);
1531 $counter++;
1532 }
1533 }
1534
1535 if ($counter == 0) {
1536 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term deleted.', 'simple-tags'), 'updated taxopress-notice');
1537 } else {
1538 add_settings_error(__CLASS__, __CLASS__, sprintf(esc_html__('%1s term(s) deleted.', 'simple-tags'), $counter), 'updated taxopress-notice');
1539 }
1540
1541 return true;
1542 }
1543
1544 public function handle_taxopress_check_delete_terms_ajax()
1545 {
1546
1547 if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), 'st-admin-js')) {
1548 wp_send_json_error(array('message' => __('Nonce verification failed.', 'simple-tags')));
1549 wp_die();
1550 }
1551
1552 global $wpdb;
1553
1554 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : 'post_tag';
1555 $number = isset($_POST['number']) ? intval($_POST['number']) : 0;
1556
1557 $taxonomy_object = get_taxonomy($taxonomy);
1558 if (
1559 !current_user_can('simple_tags')
1560 || !$taxonomy_object
1561 || empty($taxonomy_object->cap->manage_terms)
1562 || !current_user_can($taxonomy_object->cap->manage_terms)
1563 ) {
1564 wp_send_json_error(array('message' => __('Permission denied.', 'simple-tags')), 403);
1565 }
1566
1567 if ((int) $number > 100) {
1568 wp_die('Tcheater ?');
1569 }
1570
1571 if ($number > 0) {
1572 $terms = $wpdb->get_col($wpdb->prepare(
1573 "
1574 SELECT term_id FROM $wpdb->term_taxonomy
1575 WHERE taxonomy = %s AND count < %d",
1576 $taxonomy,
1577 (int) $number
1578 ));
1579
1580 $term_count = count($terms);
1581
1582 if ($term_count > 0) {
1583 wp_send_json_success(array('message' => sprintf(__('%d terms will be deleted.', 'simple-tags'), $term_count)));
1584 } else {
1585 wp_send_json_error(array('message' => __('No terms will be deleted.', 'simple-tags')));
1586 }
1587 } else {
1588 wp_send_json_error(array('message' => __('Invalid number specified.', 'simple-tags')));
1589 }
1590
1591 wp_die();
1592 }
1593
1594 /**
1595 * Method for removing terms from all or specified posts
1596 *
1597 * @param string $taxonomy
1598 * @param string $match
1599 * @param string $remove
1600 *
1601 * @return boolean
1602 * @author WebFactory Ltd
1603 */
1604 public static function removeMatchTerms($taxonomy = 'post_tag', $match = '', $remove = '')
1605 {
1606 // Helper function to extract term name (ignoring slug in brackets)
1607 $extractTermName = function ($term) {
1608 return trim(preg_replace('/\s*\(.*?\)$/', '', $term));
1609 };
1610
1611 if (trim(str_replace(',', '', stripslashes($remove))) == '') {
1612 add_settings_error(__CLASS__, __CLASS__, esc_html__('No term(s) specified for removal!', 'simple-tags'), 'error taxopress-notice');
1613 return false;
1614 }
1615
1616 $match_terms = explode(',', $match);
1617 $remove_terms = explode(',', $remove);
1618
1619 // Clean up terms by removing slugs
1620 $match_terms = array_map($extractTermName, $match_terms);
1621 $remove_terms = array_map($extractTermName, $remove_terms);
1622
1623 $match_terms = array_filter($match_terms, '_delete_empty_element');
1624 $remove_terms = array_filter($remove_terms, '_delete_empty_element');
1625
1626 // Arrays to track if terms entered is valid
1627 $valid_remove_terms = array();
1628 $invalid_remove_terms = array();
1629
1630 foreach ((array) $remove_terms as $remove_term) {
1631 $term = get_term_by('name', sanitize_text_field($remove_term), $taxonomy);
1632 if ($term) {
1633 $valid_remove_terms[] = $remove_term; // Add to valid list if the term exists
1634 } else {
1635 $invalid_remove_terms[] = $remove_term; // Collect invalid remove terms
1636 }
1637 }
1638
1639 if (empty($valid_remove_terms)) {
1640 add_settings_error(__CLASS__, __CLASS__, esc_html__('Term(s) does not exist.', 'simple-tags'), 'error taxopress-notice');
1641 return false;
1642 }
1643
1644 $counter = 0;
1645 if (!empty($match_terms)) {
1646 // Get terms ID from match terms
1647 $terms_id = array();
1648 foreach ((array) $match_terms as $match_term) {
1649 $term = get_term_by('name', sanitize_text_field($match_term), $taxonomy);
1650 if ($term) {
1651 $terms_id[] = (int) $term->term_id;
1652 }
1653 }
1654
1655 // Get object ID with terms ID
1656 $objects_id = get_objects_in_term($terms_id, $taxonomy, array('fields' => 'all_with_object_id'));
1657
1658 // Remove specified terms from matched posts
1659 foreach ((array) $objects_id as $object_id) {
1660 wp_remove_object_terms($object_id, $valid_remove_terms, $taxonomy);
1661 $counter++;
1662 }
1663
1664 clean_object_term_cache($objects_id, $taxonomy);
1665 clean_term_cache($terms_id, $taxonomy);
1666 } else {
1667 // Get all posts if no match terms were provided
1668 global $wpdb;
1669 $post_type = sanitize_key(SimpleTags_Admin::$post_type);
1670 if (!post_type_exists($post_type)) {
1671 add_settings_error(__CLASS__, __CLASS__, esc_html__('Invalid post type.', 'simple-tags'), 'error taxopress-notice');
1672 return false;
1673 }
1674 SimpleTags_Admin::$post_type = $post_type;
1675 $objects_id = $wpdb->get_col(
1676 $wpdb->prepare(
1677 "SELECT ID FROM {$wpdb->posts} WHERE (post_status = 'publish' OR post_status = 'inherit') AND post_type = %s",
1678 $post_type
1679 )
1680 );
1681
1682 // Remove valid terms for all posts
1683 foreach ((array) $objects_id as $object_id) {
1684 wp_remove_object_terms($object_id, $valid_remove_terms, $taxonomy);
1685 clean_object_term_cache($object_id, $taxonomy);
1686 clean_term_cache($valid_remove_terms, $taxonomy);
1687 $counter++;
1688 }
1689
1690 clean_object_term_cache($objects_id, $taxonomy);
1691 }
1692
1693 if ($counter == 0) {
1694 add_settings_error(__CLASS__, __CLASS__, esc_html__('No matching term found.', 'simple-tags'), 'updated taxopress-notice');
1695 } else {
1696 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');
1697 }
1698
1699 return true;
1700 }
1701
1702 public static function handle_taxopress_autocomplete_terms()
1703 {
1704 if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), 'st-admin-js')) {
1705 wp_send_json_error(['message' => __('Nonce verification failed.', 'simple-tags')]);
1706 wp_die();
1707 }
1708
1709 $taxonomy = isset($_POST['taxonomy']) ? sanitize_key(wp_unslash($_POST['taxonomy'])) : 'post_tag';
1710 $term = isset($_POST['term']) ? sanitize_text_field(wp_unslash($_POST['term'])) : '';
1711
1712 $taxonomy_object = get_taxonomy($taxonomy);
1713 if (
1714 !current_user_can('simple_tags')
1715 || !$taxonomy_object
1716 || empty($taxonomy_object->cap->manage_terms)
1717 || !current_user_can($taxonomy_object->cap->manage_terms)
1718 ) {
1719 wp_send_json_error(['message' => __('Permission denied.', 'simple-tags')], 403);
1720 }
1721
1722 $terms = get_terms([
1723 'taxonomy' => $taxonomy,
1724 'name__like' => $term,
1725 'hide_empty' => false,
1726 'number' => (int) apply_filters('taxopress_autocomplete_terms_limit', 50),
1727 'update_term_meta_cache' => false,
1728 ]);
1729
1730 $results = [];
1731 foreach ($terms as $term) {
1732 $results[] = array(
1733 'name' => html_entity_decode(
1734 $term->name,
1735 ENT_QUOTES,
1736 get_bloginfo('charset')
1737 ),
1738 'slug' => $term->slug,
1739 );
1740 }
1741
1742 wp_send_json($results);
1743 wp_die();
1744 }
1745
1746
1747 /**
1748 * Method for edit one or more terms slug
1749 *
1750 * @param string $taxonomy
1751 * @param string $names
1752 * @param string $slugs
1753 *
1754 * @return boolean
1755 * @author WebFactory Ltd
1756 */
1757 /*
1758 public static function editTermSlug( $taxonomy = 'post_tag', $names = '', $slugs = '') {
1759 if ( trim( str_replace(',', '', stripslashes($slugs)) ) == '' ) {
1760 add_settings_error( __CLASS__, __CLASS__, esc_html__('No new slug(s) specified!', 'simple-tags'), 'error' );
1761 return false;
1762 }
1763
1764 $match_names = explode(',', $names);
1765 $new_slugs = explode(',', $slugs);
1766
1767 $match_names = array_filter($match_names, '_delete_empty_element');
1768 $new_slugs = array_filter($new_slugs, '_delete_empty_element');
1769
1770 if ( count($match_names) != count($new_slugs) ) {
1771 add_settings_error( __CLASS__, __CLASS__, esc_html__('Terms number and slugs number isn\'t the same!', 'simple-tags'), 'error' );
1772 return false;
1773 } else {
1774 $counter = 0;
1775 foreach ( (array) $match_names as $i => $match_name ) {
1776 // Sanitize slug + Escape
1777 $new_slug = sanitize_title($new_slugs[$i]);
1778
1779 // Get term by name
1780 $term = get_term_by('name', $match_name, $taxonomy);
1781 if ( !$term ) {
1782 continue;
1783 }
1784
1785 // Increment
1786 $counter++;
1787
1788 // Update term
1789 wp_update_term($term->term_id, $taxonomy, array('slug' => $new_slug));
1790
1791 // Clean cache
1792 clean_term_cache($term->term_id, $taxonomy);
1793 }
1794 }
1795
1796 if ( $counter == 0 ) {
1797 add_settings_error( __CLASS__, __CLASS__, esc_html__('No slug edited.', 'simple-tags'), 'updated' );
1798 } else {
1799 add_settings_error( __CLASS__, __CLASS__, sprintf(esc_html__('%s slug(s) edited.', 'simple-tags'), $counter), 'updated' );
1800 }
1801
1802 return true;
1803 }
1804 */
1805
1806
1807 /** Singleton instance */
1808 public static function get_instance()
1809 {
1810 if (! isset(self::$instance)) {
1811 self::$instance = new self();
1812 }
1813
1814 return self::$instance;
1815 }
1816 }
1817 ?>
1818