PluginProbe
PublishPress Taxonomies – Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / 4.0.0
PublishPress Taxonomies – Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms v4.0.0
4.0.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 All 179 releases
simple-tags / inc / class.admin.manage.php

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

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