PluginProbe
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms / 3.44.0
Tag, Category, and Taxonomy Manager – Autotagger Automatically Add Terms v3.44.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 3.6.2 All 177 releases
simple-tags / inc / class.client.autoterms.php

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

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