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

class.client.autoterms.php in Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms 3.53.0, at inc/class.client.autoterms.php

1,050 lines 50.4 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,VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.Security.NonceVerification.Missing,WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter -- Legacy TaxoPress file: keep behavior unchanged while documenting existing PHPCS exceptions.
4
5 class SimpleTags_Client_Autoterms
6 {
7 /**
8 * Constructor
9 *
10 * @return void
11 * @author WebFactory Ltd
12 */
13 public function __construct()
14 {
15 if (1 === (int) SimpleTags_Plugin::get_option_value('active_auto_terms')) {
16 add_action('save_post', array(__CLASS__, 'save_post'), 12, 2);
17 add_action('post_syndicated_item', array(__CLASS__, 'save_post'), 12, 2);
18 }
19 add_filter('taxopress_filter_autoterm_content', array(__CLASS__, 'filter_taxopress_autoterm_content'), 10, 3);
20 }
21
22 /**
23 * Check post/page content for auto terms
24 *
25 * @param integer $post_id
26 * @param object $object
27 *
28 * @return boolean
29 */
30 public static function save_post($post_id = null, $object = null)
31 {
32 // Get options
33 $options = get_option(STAGS_OPTIONS_NAME_AUTO);
34
35 // user preference for this post ?
36 $meta_value = isset($_POST['exclude_autotags']) ? sanitize_text_field(wp_unslash($_POST['exclude_autotags'])) : false;
37 if ($meta_value) {
38 return false;
39 }
40
41 if (!is_object($object)) {
42 return;
43 }
44
45 if (!isset($object->post_type)) {
46 return;
47 }
48
49 // Loop option for find if autoterms is actived on any taxonomy and post type
50 $current_post_type = $object->post_type;
51 $current_post_status = $object->post_status;
52 $autoterms = taxopress_get_autoterm_data();
53 $flag = false;
54
55 foreach ($autoterms as $autoterm_data) {
56 $eligible_post_status = isset($autoterm_data['post_status']) && is_array($autoterm_data['post_status']) ? $autoterm_data['post_status'] : ['publish'];
57 $eligible_post_types = isset($autoterm_data['post_types']) && is_array($autoterm_data['post_types']) ? $autoterm_data['post_types'] : [];
58 $eligible_post_types = array_filter($eligible_post_types);
59
60 if (count($eligible_post_types) === 0) {
61 continue;
62 }
63 if (!in_array($current_post_type, $eligible_post_types)) {
64 continue;
65 }
66 if (!in_array($current_post_status, $eligible_post_status)) {
67 continue;
68 }
69 // check if auto term is enabled for post
70 if (empty($autoterm_data['autoterm_for_post'])) {
71 continue;
72 }
73
74 $autoterm_data['replace_type'] = isset($autoterm_data['post_replace_type']) ? $autoterm_data['post_replace_type'] : '';
75
76 self::auto_terms_post($object, $autoterm_data['taxonomy'], $autoterm_data, false, 'save_posts', 'st_autoterms');
77 $flag = true;
78 }
79
80 if ($flag == true) { // Clean cache ?
81 clean_post_cache($post_id);
82 }
83
84 return true;
85 }
86
87 /**
88 * Filter auto term content
89 *
90 * @param string $content Original content to be analyzed. It could include post title,
91 * content and/excerpt based on autoterms settings
92 * @param integer $post_id This is the post id
93 * @param array $options Autoterm settings
94 *
95 * @return string
96 */
97 public static function filter_taxopress_autoterm_content($content, $post_id, $settings_data)
98 {
99
100 // add taxonomies data to content
101 if (!empty($settings_data['find_in_taxonomies_custom_items']) && is_array($settings_data['find_in_taxonomies_custom_items'])) {
102 foreach ($settings_data['find_in_taxonomies_custom_items'] as $taxonomy) {
103 // Fetch all terms in the specified taxonomy
104 $terms = wp_get_post_terms($post_id, $taxonomy, array('fields' => 'names'));
105
106 // Check if there are any terms and then process them
107 if (!is_wp_error($terms) && !empty($terms)) {
108 // Join the term names with a space
109 $joined_terms = implode(' ', $terms);
110 // add data to content
111 $content .= ' ' . $joined_terms;
112 }
113 }
114 }
115
116 // add custom field data to content
117 if (!empty($settings_data['find_in_custom_fields_custom_items']) && is_array($settings_data['find_in_custom_fields_custom_items'])) {
118 foreach ($settings_data['find_in_custom_fields_custom_items'] as $metakey) {
119 // get meta key value
120 $meta_value = get_post_meta($post_id, $metakey, true);
121 if (!empty($meta_value)) {
122 // add data to content
123 $resolved_values = [];
124
125 $values = is_array($meta_value) ? $meta_value : array($meta_value);
126
127 foreach ($values as $value) {
128 if (is_numeric($value)) {
129 $term = get_term((int)$value);
130 if ($term && !is_wp_error($term) && !empty($term->name)) {
131 $resolved_values[] = $term->name;
132 continue;
133 }
134 }
135 if (is_object($value) || is_array($value)) {
136 if (is_array($value) && isset($value['name'])) {
137 $resolved_values[] = $value['name'];
138 } elseif (is_object($value) && isset($value->name)) {
139 $resolved_values[] = $value->name;
140 } else {
141 $resolved_values[] = wp_json_encode($value);
142 }
143 continue;
144 }
145
146 if (is_scalar($value)) {
147 $resolved_values[] = (string)$value;
148 }
149 }
150
151 if (!empty($resolved_values)) {
152 $resolved_string = implode(' ', $resolved_values);
153 $content .= ' ' . $resolved_string;
154 }
155 }
156 }
157 }
158
159 return $content;
160 }
161
162 /**
163 * Automatically tag a post/page from the database terms for the taxonomy specified
164 *
165 * @param object $object
166 * @param string $taxonomy
167 * @param array $options
168 * @param boolean $counter
169 *
170 * @return boolean
171 * @author WebFactory Ltd
172 */
173 public static function auto_terms_post($object, $taxonomy = 'post_tag', $options = array(), $counter = false, $action = 'save_posts', $component = 'st_autoterms')
174 {
175 global $wpdb, $added_post_term, $empty_term_messages;
176
177 if (!is_array($empty_term_messages)) {
178 $empty_term_messages = [];
179 }
180
181 if (!isset($empty_term_messages[$object->ID]['message'])) {
182 $empty_term_messages[$object->ID]['message'] = [];
183 }
184
185 $terms_to_add = array();
186
187 $exclude_autotags = get_post_meta($object->ID, '_exclude_autotags', true);
188 if ($exclude_autotags) {
189 $empty_term_messages[$object->ID]['message'][] = esc_html__('Post is excluded from Auto Term from post area.', 'simple-tags');
190 return false;
191 }
192
193 // Option exists ?
194 if ($options == false || empty($options)) {
195 $empty_term_messages[$object->ID]['message'][] = esc_html__('Invalid Auto Term settings.', 'simple-tags');
196 //update log
197 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'invalid_option');
198 return false;
199 }
200
201 if ($action == 'save_posts' && (int) $options['autoterm_target'] === 1 && get_the_terms($object->ID, $taxonomy) != false) {
202 $empty_term_messages[$object->ID]['message'][] = esc_html__('Only use Auto Terms on posts with no added terms is enabled and this post contain terms.', 'simple-tags');
203 //update log
204 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'term_only_option');
205 return false; // Skip post with terms, if term only empty post option is checked
206 }
207
208 $autoterm_exclude = isset($options['autoterm_exclude']) ? taxopress_change_to_array($options['autoterm_exclude']) : [];
209
210
211 $content_source = !empty($options['autoterm_from']) ? $options['autoterm_from'] : 'post_content_title';
212 if ($content_source === 'post_title') {
213 $content = $object->post_title;
214 } elseif ($content_source === 'post_content') {
215 $content = $object->post_content;
216 } elseif ($content_source === 'posts') {
217 $content = $object->post_content . ' ' . $object->post_title;
218 }
219
220 $html_exclusion = (!empty($options['html_exclusion']) && is_array($options['html_exclusion'])) ? $options['html_exclusion'] : [];
221 $html_exclusion_customs = !empty($options['html_exclusion_customs']) ? $options['html_exclusion_customs'] : [];
222 if (!empty($html_exclusion_customs)) {
223 $html_exclusion = array_filter(array_merge($html_exclusion, $html_exclusion_customs));
224 }
225 if (!empty($content) && count($html_exclusion) > 0) {
226 foreach ($html_exclusion as $html_tag) {
227 $pattern = "/<{$html_tag}[^>]*>.*?<\/{$html_tag}>/is";
228 $content = preg_replace($pattern, '', $content);
229 }
230 }
231
232 /**
233 * Filter auto term content
234 *
235 * @param string $content Original content to be analyzed. It could include post title,
236 * content and/excerpt based on autoterms settings
237 * @param integer $post_id This is the post id
238 * @param array $options Autoterm settings
239 */
240 $content = apply_filters('taxopress_filter_autoterm_content', $content, $object->ID, $options);
241
242 $content = trim(wp_strip_all_tags($content));
243
244 if (empty(trim($content))) {
245 $empty_term_messages[$object->ID]['message'][] = esc_html__('Auto Term content is empty. Could not suggest terms without content.', 'simple-tags');
246 //update log
247 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_post_content');
248 return false;
249 }
250
251 $autoterm_use_open_ai = isset($options['autoterm_use_open_ai']) ? (int) $options['autoterm_use_open_ai'] : 0;
252 $autoterm_use_ibm_watson = isset($options['autoterm_use_ibm_watson']) ? (int) $options['autoterm_use_ibm_watson'] : 0;
253 $autoterm_use_dandelion = isset($options['autoterm_use_dandelion']) ? (int) $options['autoterm_use_dandelion'] : 0;
254 $autoterm_use_opencalais = isset($options['autoterm_use_opencalais']) ? (int) $options['autoterm_use_opencalais'] : 0;
255 $autoterm_regex_code = !empty($options['autoterm_use_regex']) && !empty($options['terms_regex_code']) ? stripslashes($options['terms_regex_code']) : '';
256 $autoterm_use_taxonomy = !empty($options['autoterm_use_taxonomy']) && (int) $options['autoterm_use_taxonomy'] === 1;
257 $autoterm_useall = !empty($options['autoterm_useall']) && (int) $options['autoterm_useall'] === 1;
258 $autoterm_useonly = !empty($options['autoterm_useonly']) && (int) $options['autoterm_useonly'] === 1;
259
260 $autoterm_replace_type = isset($options['replace_type']) ? $options['replace_type'] : '';
261
262 $args = [
263 'post_id' => $object->ID,
264 'settings_data' => $options,
265 'content' => $content,
266 'taxonomy' => $taxonomy,
267 'clean_content' => TaxoPressAiUtilities::taxopress_clean_up_content($content),
268 'content_source' => 'autoterm_' . $content_source
269
270 ];
271
272 //Autoterm with OpenAI
273 if ($autoterm_use_open_ai > 0 && taxopress_is_pro_version()) {
274 $open_ai_results = TaxoPressAiApi::get_open_ai_results($args);
275 if (!empty($open_ai_results['results'])) {
276 $term_results = $open_ai_results['results'];
277 $data = $term_results;
278 foreach ((array) $data as $term) {
279 $term = stripslashes($term);
280
281 if (!is_string($term)) {
282 continue;
283 }
284
285 $term = trim($term);
286 if (empty($term)) {
287 continue;
288 }
289
290 //exclude if name found in exclude terms
291 if (in_array($term, $autoterm_exclude)) {
292 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by OpenAI but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
293 continue;
294 }
295
296 //add primary term
297 $add_terms = [];
298 $add_terms[$term] = $term;
299 // add term synonyms
300 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
301 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
302 if (!empty($term_synonyms)) {
303 foreach ($term_synonyms as $term_synonym) {
304 $add_terms[$term_synonym] = $term;
305 }
306 }
307 }
308
309 // add linked term
310 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
311
312 foreach ($add_terms as $find_term => $original_term) {
313 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
314
315 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
316 // The settings only apply to saving post and schedule auto terms. So, just add the terms for other component since it's suggested by the service.
317 $terms_to_add[] = $term;
318 } else {
319 if (!empty($terms_regex_code)) {
320 if (preg_match("{$terms_regex_code}", $content)) {
321 $terms_to_add[] = $term;
322 }
323 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
324 // Whole word ?
325 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
326 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
327 $terms_to_add[] = $term;
328 }
329
330 //make exception for hashtag special character
331 if (substr($find_term, 0, strlen('#')) === '#') {
332 $trim_term = ltrim($find_term, '#');
333 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
334 $terms_to_add[] = $term;
335 }
336 }
337
338 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
339 $terms_to_add[] = $term;
340 }
341 } elseif (stristr($content, $find_term)) {
342 $terms_to_add[] = $term;
343 }
344 }
345 }
346 }
347 } else {
348 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenAI', 'simple-tags') . ': ' . $open_ai_results['message'];
349 }
350 }
351
352 if ($autoterm_use_ibm_watson > 0 && taxopress_is_pro_version()) {
353 //Autoterm with IBM Watson
354 $ibm_watson_results = TaxoPressAiApi::get_ibm_watson_results($args);
355 if (!empty($ibm_watson_results['results'])) {
356 $term_results = $ibm_watson_results['results'];
357 $data = $term_results;
358 foreach ((array) $data as $term) {
359 $term = stripslashes($term);
360
361 if (!is_string($term)) {
362 continue;
363 }
364
365 $term = trim($term);
366 if (empty($term)) {
367 continue;
368 }
369
370 //exclude if name found in exclude terms
371 if (in_array($term, $autoterm_exclude)) {
372 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by IBM Watson but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
373 continue;
374 }
375
376 //add primary term
377 $add_terms = [];
378 $add_terms[$term] = $term;
379 // add term synonyms
380 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
381 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
382 if (!empty($term_synonyms)) {
383 foreach ($term_synonyms as $term_synonym) {
384 $add_terms[$term_synonym] = $term;
385 }
386 }
387 }
388
389 // add linked term
390 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
391
392 foreach ($add_terms as $find_term => $original_term) {
393 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
394
395 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
396 // The settings only apply to saving post and schedule auto terms. So, just add the terms for other component since it's suggested by the service.
397 $terms_to_add[] = $term;
398 } else {
399 if (!empty($terms_regex_code)) {
400 if (preg_match("{$terms_regex_code}", $content)) {
401 $terms_to_add[] = $term;
402 }
403 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
404 // Whole word ?
405 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
406 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
407 $terms_to_add[] = $term;
408 }
409
410 //make exception for hashtag special character
411 if (substr($find_term, 0, strlen('#')) === '#') {
412 $trim_term = ltrim($find_term, '#');
413 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
414 $terms_to_add[] = $term;
415 }
416 }
417
418 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
419 $terms_to_add[] = $term;
420 }
421 } elseif (stristr($content, $find_term)) {
422 $terms_to_add[] = $term;
423 }
424 }
425 }
426 }
427 } else {
428 $empty_term_messages[$object->ID]['message'][] = esc_html__('IBM Watson', 'simple-tags') . ': ' . $ibm_watson_results['message'];
429 }
430 }
431
432 if ($autoterm_use_dandelion > 0 && taxopress_is_pro_version()) {
433 //Autoterm with Dandelion
434 $dandelion_results = TaxoPressAiApi::get_dandelion_results($args);
435 if (!empty($dandelion_results['results'])) {
436 $term_results = $dandelion_results['results'];
437 $data = $term_results;
438 if (!empty($data)) {
439 foreach ((array) $data as $term) {
440 $term = stripslashes($term);
441
442 if (!is_string($term)) {
443 continue;
444 }
445
446 $term = trim($term);
447 if (empty($term)) {
448 continue;
449 }
450
451 //exclude if name found in exclude terms
452 if (in_array($term, $autoterm_exclude)) {
453 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by Dandelion but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
454 continue;
455 }
456
457 //add primary term
458 $add_terms = [];
459 $add_terms[$term] = $term;
460 // add term synonyms
461 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
462 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
463 if (!empty($term_synonyms)) {
464 foreach ($term_synonyms as $term_synonym) {
465 $add_terms[$term_synonym] = $term;
466 }
467 }
468 }
469
470 // add linked term
471 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
472
473 foreach ($add_terms as $find_term => $original_term) {
474 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
475
476 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
477 // The settings only apply to saving post and schedule auto terms. So, just add the terms for other component since it's suggested by the service.
478 $terms_to_add[] = $term;
479 } else {
480 if (!empty($terms_regex_code)) {
481 if (preg_match("{$terms_regex_code}", $content)) {
482 $terms_to_add[] = $term;
483 }
484 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
485 // Whole word ?
486 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
487 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
488 $terms_to_add[] = $term;
489 }
490
491 //make exception for hashtag special character
492 if (substr($find_term, 0, strlen('#')) === '#') {
493 $trim_term = ltrim($find_term, '#');
494 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
495 $terms_to_add[] = $term;
496 }
497 }
498
499 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
500 $terms_to_add[] = $term;
501 }
502 } elseif (stristr($content, $find_term)) {
503 $terms_to_add[] = $term;
504 }
505 }
506 }
507 }
508 }
509 } else {
510 $empty_term_messages[$object->ID]['message'][] = esc_html__('Dandelion', 'simple-tags') . ': ' . $dandelion_results['message'];
511 }
512 }
513
514 if ($autoterm_use_opencalais > 0 && taxopress_is_pro_version()) {
515 //Autoterm with OpenCalais
516 $open_calais_results = TaxoPressAiApi::get_open_calais_results($args);
517 if (!empty($open_calais_results['results'])) {
518 $data = $open_calais_results['results'];
519 foreach ((array) $data as $term) {
520 $term = stripslashes($term);
521
522 if (!is_string($term)) {
523 continue;
524 }
525
526 $term = trim($term);
527 if (empty($term)) {
528 continue;
529 }
530
531 //exclude if name found in exclude terms
532 if (in_array($term, $autoterm_exclude)) {
533 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by OpenCalais but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
534 continue;
535 }
536
537 //add primary term
538 $add_terms = [];
539 $add_terms[$term] = $term;
540
541 // add term synonyms
542 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
543 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
544 if (!empty($term_synonyms)) {
545 foreach ($term_synonyms as $term_synonym) {
546 $add_terms[$term_synonym] = $term;
547 }
548 }
549 }
550
551 // add linked term
552 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
553
554 foreach ($add_terms as $find_term => $original_term) {
555 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
556
557 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
558 // The settings only apply to saving post and schedule auto terms. So, just add the terms for other component since it's suggested by the service.
559 $terms_to_add[] = $term;
560 } else {
561 if (!empty($terms_regex_code)) {
562 if (preg_match("{$terms_regex_code}", $content)) {
563 $terms_to_add[] = $term;
564 }
565 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
566 // Whole word ?
567 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
568 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
569 $terms_to_add[] = $term;
570 }
571
572 //make exception for hashtag special character
573 if (substr($find_term, 0, strlen('#')) === '#') {
574 $trim_term = ltrim($find_term, '#');
575 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
576 $terms_to_add[] = $term;
577 }
578 }
579
580 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
581 $terms_to_add[] = $term;
582 }
583 } elseif (stristr($content, $find_term)) {
584 $terms_to_add[] = $term;
585 }
586 }
587 }
588 }
589 } else {
590 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenCalais', 'simple-tags') . ': ' . $open_calais_results['message'];
591 }
592 }
593
594 if ($autoterm_use_taxonomy && $autoterm_useonly && !empty($options['specific_terms'])) {
595 // Auto term with specific auto terms list
596 $terms = taxopress_sanitize_specific_terms($options['specific_terms']);
597 foreach ($terms as $term) {
598 if (!is_string($term)) {
599 continue;
600 }
601
602 $term = trim($term);
603 if (empty($term)) {
604 continue;
605 }
606
607 //exclude if name found in exclude terms
608 if (in_array($term, $autoterm_exclude)) {
609 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by Suggested terms from specific terms but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
610 continue;
611 }
612
613 //add primary term
614 $add_terms = [];
615 $add_terms[$term] = $term;
616
617 // add term synonyms
618 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
619 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
620 if (!empty($term_synonyms)) {
621 foreach ($term_synonyms as $term_synonym) {
622 $add_terms[$term_synonym] = $term;
623 }
624 }
625 }
626
627 // add linked term
628 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
629
630 foreach ($add_terms as $find_term => $original_term) {
631 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
632 // This settings must be applied to inbuilt suggested terms as we need to suggest terms that are found in content
633 if (!empty($terms_regex_code)) {
634 if (preg_match("{$terms_regex_code}", $content)) {
635 $terms_to_add[] = $term;
636 }
637 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] === 1) {
638 // Whole word ?
639 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
640 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
641 $terms_to_add[] = $term;
642 }
643
644 //make exception for hashtag special character
645 if (substr($find_term, 0, strlen('#')) === '#') {
646 $trim_term = ltrim($find_term, '#');
647 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
648 $terms_to_add[] = $term;
649 }
650 }
651
652 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] === 1 && stristr($content, '#' . $find_term)) {
653 $terms_to_add[] = $term;
654 }
655 } elseif (stristr($content, $find_term)) {
656 $terms_to_add[] = $term;
657 }
658 }
659 }
660 unset($terms, $term);
661 } elseif ($autoterm_use_taxonomy && $autoterm_useall) {
662 // Auto terms with all terms
663 // Get all terms
664 $terms = $wpdb->get_col($wpdb->prepare("SELECT DISTINCT name
665 FROM {$wpdb->terms} AS t
666 INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
667 WHERE tt.taxonomy = %s", $taxonomy));
668
669 $terms = array_unique($terms);
670
671 foreach ($terms as $term) {
672 $term = stripslashes($term);
673
674 if (!is_string($term)) {
675 continue;
676 }
677
678 $term = trim($term);
679 if (empty($term)) {
680 continue;
681 }
682
683 //exclude if name found in exclude terms
684 if (in_array($term, $autoterm_exclude)) {
685 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested by Suggested terms from taxonomy terms but excluded by Auto Term Exception settings.', 'simple-tags'), '<strong>' . $term . '</strong>');
686 continue;
687 }
688
689 //add primary term
690 $add_terms = [];
691 $add_terms[$term] = $term;
692
693 // add term synonyms
694 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
695 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
696 if (!empty($term_synonyms)) {
697 foreach ($term_synonyms as $term_synonym) {
698 $add_terms[$term_synonym] = $term;
699 }
700 }
701 }
702
703 // add linked term
704 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
705
706 foreach ($add_terms as $find_term => $original_term) {
707 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
708
709 // This settings must be applied to inbuilt suggested terms as we need to suggest terms that are found in content
710 if (!empty($terms_regex_code)) {
711 if (preg_match("{$terms_regex_code}", $content)) {
712 $terms_to_add[] = $term;
713 }
714 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
715 // Whole word ?
716 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
717 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
718 $terms_to_add[] = $term;
719 }
720
721 //make exception for hashtag special character
722 if (substr($find_term, 0, strlen('#')) === '#') {
723 $trim_term = ltrim($find_term, '#');
724 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
725 $terms_to_add[] = $term;
726 }
727 }
728
729 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
730 $terms_to_add[] = $term;
731 }
732 } elseif (stristr($content, $find_term)) {
733 $terms_to_add[] = $term;
734 }
735 }
736 }
737 }
738
739 // Append terms if terms to add
740 if (!empty($terms_to_add)) {
741 // Remove empty and duplicate elements
742 $terms_to_add = array_filter($terms_to_add, '_delete_empty_element');
743 $terms_to_add = array_unique($terms_to_add);
744
745 //auto terms limit
746 $terms_limit = isset($options['terms_limit']) ? (int) $options['terms_limit'] : 5;
747 if ($terms_limit > 0 && count($terms_to_add) > $terms_limit) {
748 $terms_to_add = array_slice($terms_to_add, 0, $terms_limit);
749 }
750
751 // remove term that already belongs to the post
752 foreach ($terms_to_add as $index => $term_name) {
753 if (has_term($term_name, $taxonomy, $object)) {
754 unset($terms_to_add[$index]);
755 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('%1s was suggested but not added because it already belongs to the post.', 'simple-tags'), '<strong>' . $term_name . '</strong>');
756 }
757 }
758
759 $terms_to_add = array_filter(array_values($terms_to_add));
760
761 if (empty($terms_to_add)) {
762 // remove term if replace type is replace and term is empty
763 if ($autoterm_replace_type == 'replace') {
764 $empty_term_messages[$object->ID]['message'][] = esc_html__('No term was suggested but all post terms were removed based on Auto Terms replace settings.', 'simple-tags');
765 wp_set_object_terms($object->ID, [], $taxonomy, true);
766 //update log
767 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_terms_remove_all');
768 } else {
769 //update log
770 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_terms');
771 }
772 return false;
773 }
774
775 if ($counter == true) {
776 // Increment counter
777 $counter = ((int) get_option('tmp_auto_terms_st')) + count($terms_to_add);
778 update_option('tmp_auto_terms_st', $counter);
779 }
780
781 if (!is_array($added_post_term)) {
782 $added_post_term = [];
783 }
784
785 $added_post_term[$object->ID] = $terms_to_add;
786
787 // Validate taxonomy exists
788 if (!taxonomy_exists($taxonomy)) {
789 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('Taxonomy "%s" does not exist.', 'simple-tags'), $taxonomy);
790 return false;
791 }
792
793 // Check if terms exist in taxonomy and create if needed
794 $terms_to_add_ids = [];
795 foreach ($terms_to_add as $term_name) {
796 $term_exists = term_exists($term_name, $taxonomy);
797 if ($term_exists) {
798 $term_id = is_array($term_exists) ? $term_exists['term_id'] : $term_exists;
799 $terms_to_add_ids[] = (int) $term_id;
800 } else {
801 $new_term = wp_insert_term($term_name, $taxonomy);
802 if (is_wp_error($new_term)) {
803 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('Error creating term "%s": %s', 'simple-tags'), $term_name, $new_term->get_error_message());
804 } else {
805 $terms_to_add_ids[] = (int) $new_term['term_id'];
806 }
807 }
808 }
809
810 clean_object_term_cache($object->ID, $taxonomy);
811
812 // Get current terms to compare later
813 $current_terms_before = wp_get_object_terms($object->ID, $taxonomy, array('fields' => 'names'));
814 if (is_wp_error($current_terms_before)) {
815 $current_terms_before = array();
816 }
817
818 // Remove save_post hooks temporarily to prevent infinite loops during term assignment
819 remove_action('save_post', array(__CLASS__, 'save_post'), 12);
820 remove_action('post_syndicated_item', array(__CLASS__, 'save_post'), 12);
821
822 $result = false;
823 $append_mode = false;
824 if (empty($terms_to_add_ids)) {
825 // Nothing to assign
826 } else {
827 $post_exists = get_post($object->ID);
828 if (!$post_exists) {
829 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('Error: Post ID %d does not exist.', 'simple-tags'), $object->ID);
830 } else {
831 }
832
833 // Check if taxonomy is registered for this post type
834 $post_type_taxonomies = get_object_taxonomies($post_exists->post_type);
835 if (in_array($taxonomy, $post_type_taxonomies)) {
836 } else {
837 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('Warning: Taxonomy "%s" is not registered for post type "%s".', 'simple-tags'), $taxonomy, $post_exists->post_type);
838 }
839
840 // Remove all filters on set_object_terms that might be blocking
841 global $wp_filter;
842 $backup_set_object_terms_filters = isset($wp_filter['set_object_terms']) ? $wp_filter['set_object_terms'] : null;
843
844 if (isset($wp_filter['set_object_terms'])) {
845 unset($wp_filter['set_object_terms']);
846 }
847
848 if ($autoterm_replace_type == '' || $autoterm_replace_type == 'append') {
849 $append_mode = true;
850 $result = wp_set_object_terms($object->ID, $terms_to_add_ids, $taxonomy, true);
851 } elseif ($autoterm_replace_type == 'append_and_replace' || $autoterm_replace_type == 'replace') {
852 $append_mode = false;
853 $result = wp_set_object_terms($object->ID, $terms_to_add_ids, $taxonomy, false);
854 }
855
856 // Restore filters
857 if ($backup_set_object_terms_filters !== null) {
858 $wp_filter['set_object_terms'] = $backup_set_object_terms_filters;
859 }
860 }
861
862 // If core API failed without WP_Error, apply DB-level fallback
863 if ($result === false && !empty($terms_to_add_ids)) {
864 global $wpdb;
865 $fallback_tt_ids = array();
866 foreach ($terms_to_add_ids as $term_id) {
867 $term_id = (int) $term_id;
868 $term_info = term_exists($term_id, $taxonomy);
869 if (is_array($term_info) && !empty($term_info['term_taxonomy_id'])) {
870 $fallback_tt_ids[] = (int) $term_info['term_taxonomy_id'];
871 }
872 }
873 $fallback_tt_ids = array_values(array_unique(array_filter($fallback_tt_ids)));
874
875 if (!empty($fallback_tt_ids)) {
876 $existing_tt_ids = $wpdb->get_col(
877 $wpdb->prepare(
878 "SELECT tr.term_taxonomy_id
879 FROM {$wpdb->term_relationships} AS tr
880 INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
881 WHERE tr.object_id = %d AND tt.taxonomy = %s",
882 $object->ID,
883 $taxonomy
884 )
885 );
886 if (!is_array($existing_tt_ids)) {
887 $existing_tt_ids = array();
888 }
889 $existing_tt_ids = array_map('intval', $existing_tt_ids);
890
891 // Compute final tt_ids based on replace/append behavior
892 if ($autoterm_replace_type == 'append_and_replace' || $autoterm_replace_type == 'replace') {
893 $final_tt_ids = $fallback_tt_ids;
894 if (!empty($existing_tt_ids)) {
895 foreach ($existing_tt_ids as $existing_tt_id) {
896 $wpdb->delete(
897 $wpdb->term_relationships,
898 array(
899 'object_id' => (int) $object->ID,
900 'term_taxonomy_id' => (int) $existing_tt_id
901 ),
902 array('%d', '%d')
903 );
904 }
905 }
906 } else {
907 $final_tt_ids = array_values(array_unique(array_merge($existing_tt_ids, $fallback_tt_ids)));
908 }
909
910 foreach ($final_tt_ids as $tt_id) {
911 $tt_id = (int) $tt_id;
912 $wpdb->query(
913 $wpdb->prepare(
914 "INSERT IGNORE INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id, term_order)
915 VALUES (%d, %d, 0)",
916 $object->ID,
917 $tt_id
918 )
919 );
920 }
921
922 if (!empty($final_tt_ids)) {
923 wp_update_term_count($final_tt_ids, $taxonomy);
924 clean_object_term_cache($object->ID, $taxonomy);
925 wp_cache_delete($object->ID, 'posts');
926 wp_cache_delete($object->ID, 'post_meta');
927 $result = $final_tt_ids;
928 }
929 } else {
930 // No valid term_taxonomy_ids resolved; nothing to do.
931 }
932 }
933
934 $current_terms_after = wp_get_object_terms($object->ID, $taxonomy, array('fields' => 'names'));
935 if (is_wp_error($current_terms_after)) {
936 $current_terms_after = array();
937 }
938
939 $newly_added_terms = array_diff($current_terms_after, $current_terms_before);
940 if (!empty($newly_added_terms)) {
941 $empty_term_messages[$object->ID]['message'][] = sprintf('Terms added: %s', esc_html(implode(', ', $newly_added_terms)));
942 }
943
944 // Re-add save_post hooks
945 if (1 === (int) SimpleTags_Plugin::get_option_value('active_auto_terms')) {
946 add_action('save_post', array(__CLASS__, 'save_post'), 12, 2);
947 add_action('post_syndicated_item', array(__CLASS__, 'save_post'), 12, 2);
948 }
949
950 // Check if wp_set_object_terms failed
951 if (is_wp_error($result)) {
952 $empty_term_messages[$object->ID]['message'][] = sprintf(esc_html__('Error adding terms: %s', 'simple-tags'), $result->get_error_message());
953 return false;
954 }
955
956 // Clean cache
957 clean_post_cache($object->ID);
958
959 //update log
960 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'success', 'terms_added');
961
962 return true;
963 } else {
964 // remove term if replace type is replace and term is empty
965 if ($autoterm_replace_type == 'replace') {
966 wp_set_object_terms($object->ID, [], $taxonomy, true);
967 }
968
969 //update log
970 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_terms_remove_all');
971 }
972
973 return false;
974 }
975
976 /**
977 * Update taxopress logs
978 *
979 * Known possible values
980 *
981 * COMPONENT: (st_autoterms)
982 * ACTION: (existing_content, save_posts, daily_cron_schedule, hourly_cron_schedule, weekly_cron_schedule)
983 * STATUS: (failed, success)
984 * STATUS MESSAGE: (invalid_option, term_only_option, empty_post_content, terms_added, empty_terms, empty_terms_remove_all)
985 *
986 * @param object $object
987 * @param string $taxonomy
988 * @param array $options
989 * @param boolean $counter
990 * @param string $action
991 * @param string $component
992 * @param array $terms_to_add
993 * @param string $status
994 * @param string $status_message
995 *
996 * @return boolean
997 * @author olatechpro
998 */
999 public static function update_taxopress_logs($object, $taxonomy = 'post_tag', $options = array(), $counter = false, $action = 'save_posts', $component = 'st_autoterms', $terms_to_add = [], $status = 'failed', $status_message = 'not_provided')
1000 {
1001
1002 if (get_option('taxopress_autoterms_logs_disabled') || !post_type_exists('taxopress_logs')) {
1003 return;
1004 }
1005
1006 $insert_post_args = array(
1007 'post_author' => get_current_user_id(),
1008 'post_title' => $object->post_title,
1009 'post_content' => $object->post_content,
1010 'post_status' => 'publish',
1011 'post_type' => 'taxopress_logs',
1012 );
1013 $post_id = wp_insert_post($insert_post_args);
1014 update_post_meta($post_id, '_taxopress_log_post_id', $object->ID);
1015 update_post_meta($post_id, '_taxopress_log_taxonomy', $taxonomy);
1016 update_post_meta($post_id, '_taxopress_log_post_type', get_post_type($object->ID));
1017 update_post_meta($post_id, '_taxopress_log_action', $action);
1018 update_post_meta($post_id, '_taxopress_log_component', $component);
1019 update_post_meta($post_id, '_taxopress_log_terms', implode(", ", $terms_to_add));
1020 update_post_meta($post_id, '_taxopress_log_status', $status);
1021 update_post_meta($post_id, '_taxopress_log_status_message', $status_message);
1022 update_post_meta($post_id, '_taxopress_log_options', $options);
1023 update_post_meta($post_id, '_taxopress_log_option_id', isset($options['ID']) ? $options['ID'] : 0);
1024
1025 //for performance reason, delete only 1 posts if more than limit instead of querying all posts
1026 $auto_terms_logs_limit = (int) get_option('taxopress_auto_terms_logs_limit', 1000);
1027
1028 $current_logs_counts = wp_count_posts('taxopress_logs');
1029 $current_logs_count = isset($current_logs_counts->publish) ? $current_logs_counts->publish : 0;
1030
1031 if (isset($current_logs_counts->publish) && (int) $current_logs_count > $auto_terms_logs_limit) {
1032 $posts = get_posts(
1033 array(
1034 'post_type' => 'taxopress_logs',
1035 'post_status' => 'publish',
1036 'posts_per_page' => 1,
1037 'orderby' => 'ID',
1038 'order' => 'ASC',
1039 'fields' => 'ids'
1040 )
1041 );
1042 if (count($posts) > 0) {
1043 foreach ($posts as $post) {
1044 wp_delete_post($post, true);
1045 }
1046 }
1047 }
1048 }
1049 }
1050