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

1,063 lines 50.9 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 $post_autoterms_status = self::get_post_feature_status($post_id, '_taxopress_autoterms_status', '_exclude_autotags');
37 if ('disabled' === $post_autoterms_status) {
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']) && 'enabled' !== $post_autoterms_status) {
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 if ('disabled' === self::get_post_feature_status($object->ID, '_taxopress_autoterms_status', '_exclude_autotags')) {
188 $empty_term_messages[$object->ID]['message'][] = esc_html__('Post is excluded from Auto Term from post area.', 'simple-tags');
189 return false;
190 }
191
192 // Option exists ?
193 if ($options == false || empty($options)) {
194 $empty_term_messages[$object->ID]['message'][] = esc_html__('Invalid Auto Term settings.', 'simple-tags');
195 //update log
196 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'invalid_option');
197 return false;
198 }
199
200 if ($action == 'save_posts' && (int) $options['autoterm_target'] === 1 && get_the_terms($object->ID, $taxonomy) != false) {
201 $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');
202 //update log
203 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'term_only_option');
204 return false; // Skip post with terms, if term only empty post option is checked
205 }
206
207 $autoterm_exclude = isset($options['autoterm_exclude']) ? taxopress_change_to_array($options['autoterm_exclude']) : [];
208
209
210 $content_source = !empty($options['autoterm_from']) ? $options['autoterm_from'] : 'post_content_title';
211 if ($content_source === 'post_title') {
212 $content = $object->post_title;
213 } elseif ($content_source === 'post_content') {
214 $content = $object->post_content;
215 } elseif ($content_source === 'posts') {
216 $content = $object->post_content . ' ' . $object->post_title;
217 }
218
219 $html_exclusion = (!empty($options['html_exclusion']) && is_array($options['html_exclusion'])) ? $options['html_exclusion'] : [];
220 $html_exclusion_customs = !empty($options['html_exclusion_customs']) ? $options['html_exclusion_customs'] : [];
221 if (!empty($html_exclusion_customs)) {
222 $html_exclusion = array_filter(array_merge($html_exclusion, $html_exclusion_customs));
223 }
224 if (!empty($content) && count($html_exclusion) > 0) {
225 foreach ($html_exclusion as $html_tag) {
226 $pattern = "/<{$html_tag}[^>]*>.*?<\/{$html_tag}>/is";
227 $content = preg_replace($pattern, '', $content);
228 }
229 }
230
231 /**
232 * Filter auto term content
233 *
234 * @param string $content Original content to be analyzed. It could include post title,
235 * content and/excerpt based on autoterms settings
236 * @param integer $post_id This is the post id
237 * @param array $options Autoterm settings
238 */
239 $content = apply_filters('taxopress_filter_autoterm_content', $content, $object->ID, $options);
240
241 $content = trim(wp_strip_all_tags($content));
242
243 if (empty(trim($content))) {
244 $empty_term_messages[$object->ID]['message'][] = esc_html__('Auto Term content is empty. Could not suggest terms without content.', 'simple-tags');
245 //update log
246 self::update_taxopress_logs($object, $taxonomy, $options, $counter, $action, $component, $terms_to_add, 'failed', 'empty_post_content');
247 return false;
248 }
249
250 $autoterm_use_open_ai = isset($options['autoterm_use_open_ai']) ? (int) $options['autoterm_use_open_ai'] : 0;
251 $autoterm_use_ibm_watson = isset($options['autoterm_use_ibm_watson']) ? (int) $options['autoterm_use_ibm_watson'] : 0;
252 $autoterm_use_dandelion = isset($options['autoterm_use_dandelion']) ? (int) $options['autoterm_use_dandelion'] : 0;
253 $autoterm_use_opencalais = isset($options['autoterm_use_opencalais']) ? (int) $options['autoterm_use_opencalais'] : 0;
254 $autoterm_regex_code = !empty($options['autoterm_use_regex']) && !empty($options['terms_regex_code']) ? stripslashes($options['terms_regex_code']) : '';
255 $autoterm_use_taxonomy = !empty($options['autoterm_use_taxonomy']) && (int) $options['autoterm_use_taxonomy'] === 1;
256 $autoterm_useall = !empty($options['autoterm_useall']) && (int) $options['autoterm_useall'] === 1;
257 $autoterm_useonly = !empty($options['autoterm_useonly']) && (int) $options['autoterm_useonly'] === 1;
258
259 $autoterm_replace_type = isset($options['replace_type']) ? $options['replace_type'] : '';
260
261 $args = [
262 'post_id' => $object->ID,
263 'settings_data' => $options,
264 'content' => $content,
265 'taxonomy' => $taxonomy,
266 'clean_content' => TaxoPressAiUtilities::taxopress_clean_up_content($content),
267 'content_source' => 'autoterm_' . $content_source
268
269 ];
270
271 //Autoterm with OpenAI
272 if ($autoterm_use_open_ai > 0 && taxopress_is_pro_version()) {
273 $open_ai_results = TaxoPressAiApi::get_open_ai_results($args);
274 if (!empty($open_ai_results['results'])) {
275 $term_results = $open_ai_results['results'];
276 $data = $term_results;
277 foreach ((array) $data as $term) {
278 $term = stripslashes($term);
279
280 if (!is_string($term)) {
281 continue;
282 }
283
284 $term = trim($term);
285 if (empty($term)) {
286 continue;
287 }
288
289 //exclude if name found in exclude terms
290 if (in_array($term, $autoterm_exclude)) {
291 $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>');
292 continue;
293 }
294
295 //add primary term
296 $add_terms = [];
297 $add_terms[$term] = $term;
298 // add term synonyms
299 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
300 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
301 if (!empty($term_synonyms)) {
302 foreach ($term_synonyms as $term_synonym) {
303 $add_terms[$term_synonym] = $term;
304 }
305 }
306 }
307
308 // add linked term
309 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
310
311 foreach ($add_terms as $find_term => $original_term) {
312 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
313
314 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
315 // 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.
316 $terms_to_add[] = $term;
317 } else {
318 if (!empty($terms_regex_code)) {
319 if (preg_match("{$terms_regex_code}", $content)) {
320 $terms_to_add[] = $term;
321 }
322 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
323 // Whole word ?
324 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
325 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
326 $terms_to_add[] = $term;
327 }
328
329 //make exception for hashtag special character
330 if (substr($find_term, 0, strlen('#')) === '#') {
331 $trim_term = ltrim($find_term, '#');
332 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
333 $terms_to_add[] = $term;
334 }
335 }
336
337 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
338 $terms_to_add[] = $term;
339 }
340 } elseif (stristr($content, $find_term)) {
341 $terms_to_add[] = $term;
342 }
343 }
344 }
345 }
346 } else {
347 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenAI', 'simple-tags') . ': ' . $open_ai_results['message'];
348 }
349 }
350
351 if ($autoterm_use_ibm_watson > 0 && taxopress_is_pro_version()) {
352 //Autoterm with IBM Watson
353 $ibm_watson_results = TaxoPressAiApi::get_ibm_watson_results($args);
354 if (!empty($ibm_watson_results['results'])) {
355 $term_results = $ibm_watson_results['results'];
356 $data = $term_results;
357 foreach ((array) $data as $term) {
358 $term = stripslashes($term);
359
360 if (!is_string($term)) {
361 continue;
362 }
363
364 $term = trim($term);
365 if (empty($term)) {
366 continue;
367 }
368
369 //exclude if name found in exclude terms
370 if (in_array($term, $autoterm_exclude)) {
371 $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>');
372 continue;
373 }
374
375 //add primary term
376 $add_terms = [];
377 $add_terms[$term] = $term;
378 // add term synonyms
379 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
380 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
381 if (!empty($term_synonyms)) {
382 foreach ($term_synonyms as $term_synonym) {
383 $add_terms[$term_synonym] = $term;
384 }
385 }
386 }
387
388 // add linked term
389 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
390
391 foreach ($add_terms as $find_term => $original_term) {
392 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
393
394 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
395 // 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.
396 $terms_to_add[] = $term;
397 } else {
398 if (!empty($terms_regex_code)) {
399 if (preg_match("{$terms_regex_code}", $content)) {
400 $terms_to_add[] = $term;
401 }
402 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
403 // Whole word ?
404 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
405 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
406 $terms_to_add[] = $term;
407 }
408
409 //make exception for hashtag special character
410 if (substr($find_term, 0, strlen('#')) === '#') {
411 $trim_term = ltrim($find_term, '#');
412 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
413 $terms_to_add[] = $term;
414 }
415 }
416
417 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
418 $terms_to_add[] = $term;
419 }
420 } elseif (stristr($content, $find_term)) {
421 $terms_to_add[] = $term;
422 }
423 }
424 }
425 }
426 } else {
427 $empty_term_messages[$object->ID]['message'][] = esc_html__('IBM Watson', 'simple-tags') . ': ' . $ibm_watson_results['message'];
428 }
429 }
430
431 if ($autoterm_use_dandelion > 0 && taxopress_is_pro_version()) {
432 //Autoterm with Dandelion
433 $dandelion_results = TaxoPressAiApi::get_dandelion_results($args);
434 if (!empty($dandelion_results['results'])) {
435 $term_results = $dandelion_results['results'];
436 $data = $term_results;
437 if (!empty($data)) {
438 foreach ((array) $data as $term) {
439 $term = stripslashes($term);
440
441 if (!is_string($term)) {
442 continue;
443 }
444
445 $term = trim($term);
446 if (empty($term)) {
447 continue;
448 }
449
450 //exclude if name found in exclude terms
451 if (in_array($term, $autoterm_exclude)) {
452 $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>');
453 continue;
454 }
455
456 //add primary term
457 $add_terms = [];
458 $add_terms[$term] = $term;
459 // add term synonyms
460 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
461 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
462 if (!empty($term_synonyms)) {
463 foreach ($term_synonyms as $term_synonym) {
464 $add_terms[$term_synonym] = $term;
465 }
466 }
467 }
468
469 // add linked term
470 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
471
472 foreach ($add_terms as $find_term => $original_term) {
473 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
474
475 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
476 // 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.
477 $terms_to_add[] = $term;
478 } else {
479 if (!empty($terms_regex_code)) {
480 if (preg_match("{$terms_regex_code}", $content)) {
481 $terms_to_add[] = $term;
482 }
483 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
484 // Whole word ?
485 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
486 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
487 $terms_to_add[] = $term;
488 }
489
490 //make exception for hashtag special character
491 if (substr($find_term, 0, strlen('#')) === '#') {
492 $trim_term = ltrim($find_term, '#');
493 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
494 $terms_to_add[] = $term;
495 }
496 }
497
498 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
499 $terms_to_add[] = $term;
500 }
501 } elseif (stristr($content, $find_term)) {
502 $terms_to_add[] = $term;
503 }
504 }
505 }
506 }
507 }
508 } else {
509 $empty_term_messages[$object->ID]['message'][] = esc_html__('Dandelion', 'simple-tags') . ': ' . $dandelion_results['message'];
510 }
511 }
512
513 if ($autoterm_use_opencalais > 0 && taxopress_is_pro_version()) {
514 //Autoterm with OpenCalais
515 $open_calais_results = TaxoPressAiApi::get_open_calais_results($args);
516 if (!empty($open_calais_results['results'])) {
517 $data = $open_calais_results['results'];
518 foreach ((array) $data as $term) {
519 $term = stripslashes($term);
520
521 if (!is_string($term)) {
522 continue;
523 }
524
525 $term = trim($term);
526 if (empty($term)) {
527 continue;
528 }
529
530 //exclude if name found in exclude terms
531 if (in_array($term, $autoterm_exclude)) {
532 $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>');
533 continue;
534 }
535
536 //add primary term
537 $add_terms = [];
538 $add_terms[$term] = $term;
539
540 // add term synonyms
541 if (is_array($options) && isset($options['synonyms_term']) && (int) $options['synonyms_term'] > 0) {
542 $term_synonyms = taxopress_get_term_synonyms($term, $taxonomy);
543 if (!empty($term_synonyms)) {
544 foreach ($term_synonyms as $term_synonym) {
545 $add_terms[$term_synonym] = $term;
546 }
547 }
548 }
549
550 // add linked term
551 $add_terms = taxopress_add_linked_term_options($add_terms, $term, $taxonomy, false, true);
552
553 foreach ($add_terms as $find_term => $original_term) {
554 $terms_regex_code = !empty($autoterm_regex_code) ? str_replace('{term}', preg_quote($find_term), $autoterm_regex_code) : '';
555
556 if (!in_array($action, ['save_posts', 'hourly_cron_schedule', 'daily_cron_schedule'])) {
557 // 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.
558 $terms_to_add[] = $term;
559 } else {
560 if (!empty($terms_regex_code)) {
561 if (preg_match("{$terms_regex_code}", $content)) {
562 $terms_to_add[] = $term;
563 }
564 } elseif (isset($options['autoterm_word']) && (int) $options['autoterm_word'] == 1) {
565 // Whole word ?
566 //if (preg_match("/\b" . preg_quote($find_term) . "\b/i", $content)) {
567 if (preg_match("#\b" . preg_quote($find_term) . "\b#i", $content)) {
568 $terms_to_add[] = $term;
569 }
570
571 //make exception for hashtag special character
572 if (substr($find_term, 0, strlen('#')) === '#') {
573 $trim_term = ltrim($find_term, '#');
574 if (preg_match("/\B(\#+$trim_term\b)(?!;)/i", $content)) {
575 $terms_to_add[] = $term;
576 }
577 }
578
579 if (isset($options['autoterm_hash']) && (int) $options['autoterm_hash'] == 1 && stristr($content, '#' . $find_term)) {
580 $terms_to_add[] = $term;
581 }
582 } elseif (stristr($content, $find_term)) {
583 $terms_to_add[] = $term;
584 }
585 }
586 }
587 }
588 } else {
589 $empty_term_messages[$object->ID]['message'][] = esc_html__('OpenCalais', 'simple-tags') . ': ' . $open_calais_results['message'];
590 }
591 }
592
593 if ($autoterm_use_taxonomy && $autoterm_useonly && !empty($options['specific_terms'])) {
594 // Auto term with specific auto terms list
595 $terms = taxopress_sanitize_specific_terms($options['specific_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 $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 if (get_option('taxopress_autoterms_logs_disabled') || !post_type_exists('taxopress_logs')) {
1001 return;
1002 }
1003
1004 $insert_post_args = array(
1005 'post_author' => get_current_user_id(),
1006 'post_title' => $object->post_title,
1007 'post_content' => $object->post_content,
1008 'post_status' => 'publish',
1009 'post_type' => 'taxopress_logs',
1010 );
1011 $post_id = wp_insert_post($insert_post_args);
1012 update_post_meta($post_id, '_taxopress_log_post_id', $object->ID);
1013 update_post_meta($post_id, '_taxopress_log_taxonomy', $taxonomy);
1014 update_post_meta($post_id, '_taxopress_log_post_type', get_post_type($object->ID));
1015 update_post_meta($post_id, '_taxopress_log_action', $action);
1016 update_post_meta($post_id, '_taxopress_log_component', $component);
1017 update_post_meta($post_id, '_taxopress_log_terms', implode(", ", $terms_to_add));
1018 update_post_meta($post_id, '_taxopress_log_status', $status);
1019 update_post_meta($post_id, '_taxopress_log_status_message', $status_message);
1020 update_post_meta($post_id, '_taxopress_log_options', $options);
1021 update_post_meta($post_id, '_taxopress_log_option_id', isset($options['ID']) ? $options['ID'] : 0);
1022
1023 //for performance reason, delete only 1 posts if more than limit instead of querying all posts
1024 $auto_terms_logs_limit = (int) get_option('taxopress_auto_terms_logs_limit', 1000);
1025
1026 $current_logs_counts = wp_count_posts('taxopress_logs');
1027 $current_logs_count = isset($current_logs_counts->publish) ? $current_logs_counts->publish : 0;
1028
1029 if (isset($current_logs_counts->publish) && (int) $current_logs_count > $auto_terms_logs_limit) {
1030 $posts = get_posts(
1031 array(
1032 'post_type' => 'taxopress_logs',
1033 'post_status' => 'publish',
1034 'posts_per_page' => 1,
1035 'orderby' => 'ID',
1036 'order' => 'ASC',
1037 'fields' => 'ids'
1038 )
1039 );
1040 if (count($posts) > 0) {
1041 foreach ($posts as $post) {
1042 wp_delete_post($post, true);
1043 }
1044 }
1045 }
1046 }
1047
1048 private static function get_post_feature_status($post_id, $status_meta_key, $legacy_disable_meta_key = '')
1049 {
1050 $status = get_post_meta($post_id, $status_meta_key, true);
1051
1052 if (in_array($status, ['default', 'enabled', 'disabled'], true)) {
1053 return $status;
1054 }
1055
1056 if ($legacy_disable_meta_key && get_post_meta($post_id, $legacy_disable_meta_key, true)) {
1057 return 'disabled';
1058 }
1059
1060 return 'default';
1061 }
1062 }
1063