PluginProbe
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / 3.51.0
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms v3.51.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.51.0, at inc/class.client.autoterms.php

1,050 lines 51.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class SimpleTags_Client_Autoterms
4 {
5 /**
6 * Constructor
7 *
8 * @return void
9 * @author WebFactory Ltd
10 */
11 public function __construct()
12 {
13 if (1 === (int) SimpleTags_Plugin::get_option_value('active_auto_terms')) {
14 add_action('save_post', array(__CLASS__, 'save_post'), 12, 2);
15 add_action('post_syndicated_item', array(__CLASS__, 'save_post'), 12, 2);
16 }
17 add_filter('taxopress_filter_autoterm_content', array(__CLASS__, 'filter_taxopress_autoterm_content'), 10, 3);
18 }
19
20 /**
21 * Check post/page content for auto terms
22 *
23 * @param integer $post_id
24 * @param object $object
25 *
26 * @return boolean
27 */
28 public static function save_post($post_id = null, $object = null)
29 {
30 // Get options
31 $options = get_option(STAGS_OPTIONS_NAME_AUTO);
32
33 // user preference for this post ?
34 $meta_value = isset($_POST['exclude_autotags']) ? sanitize_text_field($_POST['exclude_autotags']) : false;
35 if ($meta_value) {
36 return false;
37 }
38
39 if (!is_object($object)) {
40 return;
41 }
42
43 if (!isset($object->post_type)) {
44 return;
45 }
46
47 // Loop option for find if autoterms is actived on any taxonomy and post type
48 $current_post_type = $object->post_type;
49 $current_post_status = $object->post_status;
50 $autoterms = taxopress_get_autoterm_data();
51 $flag = false;
52
53 foreach ($autoterms as $autoterm_data) {
54 $eligible_post_status = isset($autoterm_data['post_status']) && is_array($autoterm_data['post_status']) ? $autoterm_data['post_status'] : ['publish'];
55 $eligible_post_types = isset($autoterm_data['post_types']) && is_array($autoterm_data['post_types']) ? $autoterm_data['post_types'] : [];
56 $eligible_post_types = array_filter($eligible_post_types);
57
58 if (count($eligible_post_types) === 0) {
59 continue;
60 }
61 if (!in_array($current_post_type, $eligible_post_types)) {
62 continue;
63 }
64 if (!in_array($current_post_status, $eligible_post_status)) {
65 continue;
66 }
67 // check if auto term is enabled for post
68 if (empty($autoterm_data['autoterm_for_post'])) {
69 continue;
70 }
71
72 $autoterm_data['replace_type'] = isset($autoterm_data['post_replace_type']) ? $autoterm_data['post_replace_type'] : '';
73
74 self::auto_terms_post($object, $autoterm_data['taxonomy'], $autoterm_data, false, 'save_posts', 'st_autoterms');
75 $flag = true;
76 }
77
78 if ($flag == true) { // Clean cache ?
79 clean_post_cache($post_id);
80 }
81
82 return true;
83 }
84
85 /**
86 * Filter auto term content
87 *
88 * @param string $content Original content to be analyzed. It could include post title,
89 * content and/excerpt based on autoterms settings
90 * @param integer $post_id This is the post id
91 * @param array $options Autoterm settings
92 *
93 * @return string
94 */
95 public static function filter_taxopress_autoterm_content($content, $post_id, $settings_data)
96 {
97
98 // add taxonomies data to content
99 if (!empty($settings_data['find_in_taxonomies_custom_items']) && is_array($settings_data['find_in_taxonomies_custom_items'])) {
100 foreach ($settings_data['find_in_taxonomies_custom_items'] as $taxonomy) {
101 // Fetch all terms in the specified taxonomy
102 $terms = wp_get_post_terms($post_id, $taxonomy, array('fields' => 'names'));
103
104 // Check if there are any terms and then process them
105 if (!is_wp_error($terms) && !empty($terms)) {
106 // Join the term names with a space
107 $joined_terms = implode(' ', $terms);
108 // add data to content
109 $content .= ' ' . $joined_terms;
110 }
111 }
112 }
113
114 // add custom field data to content
115 if (!empty($settings_data['find_in_custom_fields_custom_items']) && is_array($settings_data['find_in_custom_fields_custom_items'])) {
116 foreach ($settings_data['find_in_custom_fields_custom_items'] as $metakey) {
117 // get meta key value
118 $meta_value = get_post_meta($post_id, $metakey, true);
119 if (!empty($meta_value)) {
120 // add data to content
121 $resolved_values = [];
122
123 $values = is_array($meta_value) ? $meta_value : array($meta_value);
124
125 foreach ($values as $value) {
126 if (is_numeric($value)) {
127 $term = get_term((int)$value);
128 if ($term && !is_wp_error($term) && !empty($term->name)) {
129 $resolved_values[] = $term->name;
130 continue;
131 }
132 }
133 if (is_object($value) || is_array($value)) {
134 if (is_array($value) && isset($value['name'])) {
135 $resolved_values[] = $value['name'];
136 } elseif (is_object($value) && isset($value->name)) {
137 $resolved_values[] = $value->name;
138 } else {
139 $resolved_values[] = wp_json_encode($value);
140 }
141 continue;
142 }
143
144 if (is_scalar($value)) {
145 $resolved_values[] = (string)$value;
146 }
147 }
148
149 if (!empty($resolved_values)) {
150 $resolved_string = implode(' ', $resolved_values);
151 $content .= ' ' . $resolved_string;
152 }
153 }
154 }
155 }
156
157 return $content;
158 }
159
160 /**
161 * Automatically tag a post/page from the database terms for the taxonomy specified
162 *
163 * @param object $object
164 * @param string $taxonomy
165 * @param array $options
166 * @param boolean $counter
167 *
168 * @return boolean
169 * @author WebFactory Ltd
170 */
171 public static function auto_terms_post($object, $taxonomy = 'post_tag', $options = array(), $counter = false, $action = 'save_posts', $component = 'st_autoterms')
172 {
173 global $wpdb, $added_post_term, $empty_term_messages;
174
175 if (!is_array($empty_term_messages)) {
176 $empty_term_messages = [];
177 }
178
179 if (!isset($empty_term_messages[$object->ID]['message'])) {
180 $empty_term_messages[$object->ID]['message'] = [];
181 }
182
183 $terms_to_add = array();
184
185 $exclude_autotags = get_post_meta($object->ID, '_exclude_autotags', true);
186 if ($exclude_autotags) {
187 $empty_term_messages[$object->ID]['message'][] = esc_html__('Post is excluded from Auto Term from post area.', 'simple-tags');
188 return false;
189 }
190
191 // Option exists ?
192 if ($options == false || empty($options)) {
193 $empty_term_messages[$object->ID]['message'][] = esc_html__('Invalid Auto Term settings.', 'simple-tags');
194 //update log
195 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'invalid_option');
196 return false;
197 }
198
199 if ($action == 'save_posts' && (int) $options['autoterm_target'] === 1 && get_the_terms($object->ID, $taxonomy) != false) {
200 $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');
201 //update log
202 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'term_only_option');
203 return false; // Skip post with terms, if term only empty post option is checked
204 }
205
206 $autoterm_exclude = isset($options['autoterm_exclude']) ? taxopress_change_to_array($options['autoterm_exclude']) : [];
207
208
209 $content_source = !empty($options['autoterm_from']) ? $options['autoterm_from'] : 'post_content_title';
210 if ($content_source === 'post_title') {
211 $content = $object->post_title;
212 } elseif ($content_source === 'post_content') {
213 $content = $object->post_content;
214 } elseif ($content_source === 'posts') {
215 $content = $object->post_content . ' ' . $object->post_title;
216 }
217
218 $html_exclusion = (!empty($options['html_exclusion']) && is_array($options['html_exclusion'])) ? $options['html_exclusion'] : [];
219 $html_exclusion_customs = !empty($options['html_exclusion_customs']) ? $options['html_exclusion_customs'] : [];
220 if (!empty($html_exclusion_customs)) {
221 $html_exclusion = array_filter(array_merge($html_exclusion, $html_exclusion_customs));
222 }
223 if (!empty($content) && count($html_exclusion) > 0) {
224 foreach ($html_exclusion as $html_tag) {
225 $pattern = "/<{$html_tag}[^>]*>.*?<\/{$html_tag}>/is";
226 $content = preg_replace($pattern, '', $content);
227 }
228 }
229
230 /**
231 * Filter auto term content
232 *
233 * @param string $content Original content to be analyzed. It could include post title,
234 * content and/excerpt based on autoterms settings
235 * @param integer $post_id This is the post id
236 * @param array $options Autoterm settings
237 */
238 $content = apply_filters('taxopress_filter_autoterm_content', $content, $object->ID, $options);
239
240 $content = trim(strip_tags($content));
241
242 if (empty(trim($content))) {
243 $empty_term_messages[$object->ID]['message'][] = esc_html__('Auto Term content is empty. Could not suggest terms without content.', 'simple-tags');
244 //update log
245 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_post_content');
246 return false;
247 }
248
249 $autoterm_use_open_ai = isset($options['autoterm_use_open_ai']) ? (int) $options['autoterm_use_open_ai'] : 0;
250 $autoterm_use_ibm_watson = isset($options['autoterm_use_ibm_watson']) ? (int) $options['autoterm_use_ibm_watson'] : 0;
251 $autoterm_use_dandelion = isset($options['autoterm_use_dandelion']) ? (int) $options['autoterm_use_dandelion'] : 0;
252 $autoterm_use_opencalais = isset($options['autoterm_use_opencalais']) ? (int) $options['autoterm_use_opencalais'] : 0;
253 $autoterm_regex_code = !empty($options['autoterm_use_regex']) && !empty($options['terms_regex_code']) ? stripslashes($options['terms_regex_code']) : '';
254 $autoterm_use_taxonomy = !empty($options['autoterm_use_taxonomy']) && (int) $options['autoterm_use_taxonomy'] === 1;
255 $autoterm_useall = !empty($options['autoterm_useall']) && (int) $options['autoterm_useall'] === 1;
256 $autoterm_useonly = !empty($options['autoterm_useonly']) && (int) $options['autoterm_useonly'] === 1;
257
258 $autoterm_replace_type = isset($options['replace_type']) ? $options['replace_type'] : '';
259
260 $args = [
261 'post_id' => $object->ID,
262 'settings_data' => $options,
263 'content' => $content,
264 'taxonomy' => $taxonomy,
265 'clean_content' => TaxoPressAiUtilities::taxopress_clean_up_content($content),
266 'content_source' => 'autoterm_' . $content_source
267
268 ];
269
270 //Autoterm with OpenAI
271 if ($autoterm_use_open_ai > 0 && taxopress_is_pro_version()) {
272 $open_ai_results = TaxoPressAiApi::get_open_ai_results($args);
273 if (!empty($open_ai_results['results'])) {
274 $term_results = $open_ai_results['results'];
275 $data = $term_results;
276 foreach ((array) $data as $term) {
277 $term = stripslashes($term);
278
279 if (!is_string($term)) {
280 continue;
281 }
282
283 $term = trim($term);
284 if (empty($term)) {
285 continue;
286 }
287
288 //exclude if name found in exclude terms
289 if (in_array($term, $autoterm_exclude)) {
290 $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>');
291 continue;
292 }
293
294 //add primary term
295 $add_terms = [];
296 $add_terms[$term] = $term;
297 // add term synonyms
298 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
299 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
300 if (!empty($term_synonyms)) {
301 foreach ($term_synonyms as $term_synonym) {
302 $add_terms[$term_synonym] = $term;
303 }
304 }
305 }
306
307 // add linked term
308 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
309
310 foreach ($add_terms as $find_term => $original_term) {
311 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
312
313 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
314 // 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.
315 $terms_to_add[] = $term;
316 } else {
317 if (!empty($terms_regex_code)) {
318 if (preg_match("{$terms_regex_code}", $content)) {
319 $terms_to_add[] = $term;
320 }
321 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
322 // Whole word ?
323 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
324 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
325 $terms_to_add[] = $term;
326 }
327
328 //make exception for hashtag special character
329 if (substr($find_term, 0, strlen('#')) === '#') {
330 $trim_term = ltrim($find_term, '#');
331 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
332 $terms_to_add[] = $term;
333 }
334 }
335
336 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
337 $terms_to_add[] = $term;
338 }
339 } elseif (stristr($content, $find_term)) {
340 $terms_to_add[] = $term;
341 }
342 }
343 }
344 }
345 } else {
346 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenAI', 'simple-tags') . ': ' . $open_ai_results['message'];
347 }
348 }
349
350 if ($autoterm_use_ibm_watson > 0 && taxopress_is_pro_version()) {
351 //Autoterm with IBM Watson
352 $ibm_watson_results = TaxoPressAiApi::get_ibm_watson_results($args);
353 if (!empty($ibm_watson_results['results'])) {
354 $term_results = $ibm_watson_results['results'];
355 $data = $term_results;
356 foreach ((array) $data as $term) {
357 $term = stripslashes($term);
358
359 if (!is_string($term)) {
360 continue;
361 }
362
363 $term = trim($term);
364 if (empty($term)) {
365 continue;
366 }
367
368 //exclude if name found in exclude terms
369 if (in_array($term, $autoterm_exclude)) {
370 $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>');
371 continue;
372 }
373
374 //add primary term
375 $add_terms = [];
376 $add_terms[$term] = $term;
377 // add term synonyms
378 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
379 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
380 if (!empty($term_synonyms)) {
381 foreach ($term_synonyms as $term_synonym) {
382 $add_terms[$term_synonym] = $term;
383 }
384 }
385 }
386
387 // add linked term
388 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
389
390 foreach ($add_terms as $find_term => $original_term) {
391 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
392
393 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
394 // 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.
395 $terms_to_add[] = $term;
396 } else {
397 if (!empty($terms_regex_code)) {
398 if (preg_match("{$terms_regex_code}", $content)) {
399 $terms_to_add[] = $term;
400 }
401 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
402 // Whole word ?
403 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
404 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
405 $terms_to_add[] = $term;
406 }
407
408 //make exception for hashtag special character
409 if (substr($find_term, 0, strlen('#')) === '#') {
410 $trim_term = ltrim($find_term, '#');
411 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
412 $terms_to_add[] = $term;
413 }
414 }
415
416 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
417 $terms_to_add[] = $term;
418 }
419 } elseif (stristr($content, $find_term)) {
420 $terms_to_add[] = $term;
421 }
422 }
423 }
424 }
425 } else {
426 $empty_term_messages[$object->ID]['message'][] = esc_html__('IBM Watson', 'simple-tags') . ': ' . $ibm_watson_results['message'];
427 }
428 }
429
430 if ($autoterm_use_dandelion > 0 && taxopress_is_pro_version()) {
431 //Autoterm with Dandelion
432 $dandelion_results = TaxoPressAiApi::get_dandelion_results($args);
433 if (!empty($dandelion_results['results'])) {
434 $term_results = $dandelion_results['results'];
435 $data = $term_results;
436 if (!empty($data)) {
437 foreach ((array) $data as $term) {
438 $term = stripslashes($term);
439
440 if (!is_string($term)) {
441 continue;
442 }
443
444 $term = trim($term);
445 if (empty($term)) {
446 continue;
447 }
448
449 //exclude if name found in exclude terms
450 if (in_array($term, $autoterm_exclude)) {
451 $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>');
452 continue;
453 }
454
455 //add primary term
456 $add_terms = [];
457 $add_terms[$term] = $term;
458 // add term synonyms
459 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
460 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
461 if (!empty($term_synonyms)) {
462 foreach ($term_synonyms as $term_synonym) {
463 $add_terms[$term_synonym] = $term;
464 }
465 }
466 }
467
468 // add linked term
469 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
470
471 foreach ($add_terms as $find_term => $original_term) {
472 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
473
474 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
475 // 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.
476 $terms_to_add[] = $term;
477 } else {
478 if (!empty($terms_regex_code)) {
479 if (preg_match("{$terms_regex_code}", $content)) {
480 $terms_to_add[] = $term;
481 }
482 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
483 // Whole word ?
484 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
485 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
486 $terms_to_add[] = $term;
487 }
488
489 //make exception for hashtag special character
490 if (substr($find_term, 0, strlen('#')) === '#') {
491 $trim_term = ltrim($find_term, '#');
492 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
493 $terms_to_add[] = $term;
494 }
495 }
496
497 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
498 $terms_to_add[] = $term;
499 }
500 } elseif (stristr($content, $find_term)) {
501 $terms_to_add[] = $term;
502 }
503 }
504 }
505 }
506 }
507 } else {
508 $empty_term_messages[$object->ID]['message'][] = esc_html__('Dandelion', 'simple-tags') . ': ' . $dandelion_results['message'];
509 }
510 }
511
512 if ($autoterm_use_opencalais > 0 && taxopress_is_pro_version()) {
513 //Autoterm with OpenCalais
514 $open_calais_results = TaxoPressAiApi::get_open_calais_results($args);
515 if (!empty($open_calais_results['results'])) {
516 $data = $open_calais_results['results'];
517 foreach ((array) $data as $term) {
518 $term = stripslashes($term);
519
520 if (!is_string($term)) {
521 continue;
522 }
523
524 $term = trim($term);
525 if (empty($term)) {
526 continue;
527 }
528
529 //exclude if name found in exclude terms
530 if (in_array($term, $autoterm_exclude)) {
531 $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>');
532 continue;
533 }
534
535 //add primary term
536 $add_terms = [];
537 $add_terms[$term] = $term;
538
539 // add term synonyms
540 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
541 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
542 if (!empty($term_synonyms)) {
543 foreach ($term_synonyms as $term_synonym) {
544 $add_terms[$term_synonym] = $term;
545 }
546 }
547 }
548
549 // add linked term
550 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
551
552 foreach ($add_terms as $find_term => $original_term) {
553 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
554
555 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
556 // 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.
557 $terms_to_add[] = $term;
558 } else {
559 if (!empty($terms_regex_code)) {
560 if (preg_match("{$terms_regex_code}", $content)) {
561 $terms_to_add[] = $term;
562 }
563 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
564 // Whole word ?
565 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
566 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
567 $terms_to_add[] = $term;
568 }
569
570 //make exception for hashtag special character
571 if (substr($find_term, 0, strlen('#')) === '#') {
572 $trim_term = ltrim($find_term, '#');
573 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
574 $terms_to_add[] = $term;
575 }
576 }
577
578 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
579 $terms_to_add[] = $term;
580 }
581 } elseif (stristr($content, $find_term)) {
582 $terms_to_add[] = $term;
583 }
584 }
585 }
586 }
587 } else {
588 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenCalais', 'simple-tags') . ': ' . $open_calais_results['message'];
589 }
590 }
591
592 if ($autoterm_use_taxonomy && $autoterm_useonly && !empty($options['specific_terms'])) {
593 // Auto term with specific auto terms list
594 $terms = maybe_unserialize($options['specific_terms']);
595 $terms = taxopress_change_to_array($terms);
596 foreach ($terms as $term) {
597 if (!is_string($term)) {
598 continue;
599 }
600
601 $term = trim($term);
602 if (empty($term)) {
603 continue;
604 }
605
606 //exclude if name found in exclude terms
607 if (in_array($term, $autoterm_exclude)) {
608 $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>');
609 continue;
610 }
611
612 //add primary term
613 $add_terms = [];
614 $add_terms[$term] = $term;
615
616 // add term synonyms
617 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
618 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
619 if (!empty($term_synonyms)) {
620 foreach ($term_synonyms as $term_synonym) {
621 $add_terms[$term_synonym] = $term;
622 }
623 }
624 }
625
626 // add linked term
627 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
628
629 foreach ($add_terms as $find_term => $original_term) {
630 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
631 // This settings must be applied to inbuilt suggested terms as we need to suggest terms that are found in content
632 if (!empty($terms_regex_code)) {
633 if (preg_match("{$terms_regex_code}", $content)) {
634 $terms_to_add[] = $term;
635 }
636 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] === 1) {
637 // Whole word ?
638 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
639 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
640 $terms_to_add[] = $term;
641 }
642
643 //make exception for hashtag special character
644 if (substr($find_term, 0, strlen('#')) === '#') {
645 $trim_term = ltrim($find_term, '#');
646 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
647 $terms_to_add[] = $term;
648 }
649 }
650
651 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] === 1 && stristr($content, '#' . $find_term)) {
652 $terms_to_add[] = $term;
653 }
654 } elseif (stristr($content, $find_term)) {
655 $terms_to_add[] = $term;
656 }
657 }
658 }
659 unset($terms, $term);
660 } elseif ($autoterm_use_taxonomy && $autoterm_useall) {
661 // Auto terms with all terms
662 // Get all terms
663 $terms = $wpdb->get_col($wpdb->prepare("SELECT DISTINCT name
664 FROM {$wpdb->terms} AS t
665 INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
666 WHERE tt.taxonomy = %s", $taxonomy));
667
668 $terms = array_unique($terms);
669
670 foreach ($terms as $term) {
671 $term = stripslashes($term);
672
673 if (!is_string($term)) {
674 continue;
675 }
676
677 $term = trim($term);
678 if (empty($term)) {
679 continue;
680 }
681
682 //exclude if name found in exclude terms
683 if (in_array($term, $autoterm_exclude)) {
684 $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>');
685 continue;
686 }
687
688 //add primary term
689 $add_terms = [];
690 $add_terms[$term] = $term;
691
692 // add term synonyms
693 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
694 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
695 if (!empty($term_synonyms)) {
696 foreach ($term_synonyms as $term_synonym) {
697 $add_terms[$term_synonym] = $term;
698 }
699 }
700 }
701
702 // add linked term
703 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
704
705 foreach ($add_terms as $find_term => $original_term) {
706 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
707
708 // This settings must be applied to inbuilt suggested terms as we need to suggest terms that are found in content
709 if (!empty($terms_regex_code)) {
710 if (preg_match("{$terms_regex_code}", $content)) {
711 $terms_to_add[] = $term;
712 }
713 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
714 // Whole word ?
715 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
716 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
717
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