is_taxonomy_page()) {
return;
}
// Cache the autolink settings to avoid repeated database calls
static $enabled_taxonomies = null;
if ($enabled_taxonomies === null) {
$autolink_settings = taxopress_get_autolink_data();
$enabled_taxonomies = $this->get_enabled_taxonomies($autolink_settings);
}
// Only get taxonomies that are actually enabled
$taxonomies = get_taxonomies([], 'objects');
foreach ($enabled_taxonomies as $taxonomy_name) {
if (isset($taxonomies[$taxonomy_name])) {
add_action("{$taxonomy_name}_edit_form_fields", [$this, 'taxopress_add_custom_url_field']);
add_action("{$taxonomy_name}_add_form_fields", [$this, 'taxopress_add_custom_url_field_new']);
add_action("edited_{$taxonomy_name}", [$this, 'taxopress_save_custom_url_field']);
add_action("created_{$taxonomy_name}", [$this, 'taxopress_save_custom_url_field']);
}
}
}
private function is_taxonomy_page()
{
global $pagenow;
return in_array($pagenow, ['edit-tags.php', 'term.php']);
}
private function get_enabled_taxonomies($autolink_settings)
{
$default_enabled_taxonomies = ['post_tag', 'category'];
$enabled_taxonomies = [];
$has_setting_saved = false;
foreach ($autolink_settings as $setting) {
if (array_key_exists('enable_customurl_field', $setting)) {
$has_setting_saved = true;
$enabled_taxonomies = is_array($setting['enable_customurl_field']) ? $setting['enable_customurl_field'] : [];
break;
}
}
return $has_setting_saved ? array_unique($enabled_taxonomies) : $default_enabled_taxonomies;
}
/**
* Stock posts ID as soon as possible
* TODO: test if post_type allow post_tag before keep post ID
*
* @param array $posts
*
* @return array
*/
public static function the_posts($posts)
{
if (!empty($posts) && is_array($posts)) {
foreach ((array) $posts as $post) {
self::$posts[] = (int) $post->ID;
}
self::$posts = array_unique(self::$posts);
}
return $posts;
}
/**
* Get tags from current post views
*
* @return array
*/
public static function get_tags_from_current_posts($options = false)
{
if (is_array(self::$posts) && count(self::$posts) > 0) {
// Generate SQL from post id
$postlist = implode("', '", self::$posts);
// Generate key cache
$key = md5(maybe_serialize($postlist));
$results = array();
if ($options) {
$term_taxonomy = $options['taxonomy'];
} else {
$term_taxonomy = 'post_tag';
}
// Get cache if exist
$cache = wp_cache_get('generate_keywords', 'simple-tags');
if ($options || false === $cache) {
if ($cache === false) {
$cache = [];
}
foreach (self::$posts as $object_id) {
// Get terms
$terms = get_object_term_cache($object_id, $term_taxonomy);
if (false === $terms || is_wp_error($terms)) {
$terms = wp_get_object_terms($object_id, $term_taxonomy);
}
if (false !== $terms && !is_wp_error($terms)) {
$results = array_merge($results, $terms);
}
}
$cache[$key] = $results;
wp_cache_set('generate_keywords', $cache, 'simple-tags');
} else {
if (isset($cache[$key])) {
return $cache[$key];
}
}
return $results;
}
return array();
}
/**
* Get all available local tags
*
* @return array
*/
public static function get_all_post_tags($options = false)
{
if (is_array(self::$posts) && count(self::$posts) > 0) {
// Generate SQL from post id
$postlist = implode("', '", self::$posts);
// Generate key cache
$key = md5(maybe_serialize($postlist));
if ($options) {
$term_taxonomy = $options['taxonomy'];
} else {
$term_taxonomy = 'post_tag';
}
$results = get_tags(['taxonomy' => $term_taxonomy, 'hide_empty' => false]);
// Get cache if exist
$cache = wp_cache_get('generate_keywords', 'simple-tags');
if ($options || false === $cache) {
foreach (self::$posts as $object_id) {
// Get terms
$terms = get_object_term_cache($object_id, $term_taxonomy);
if (false === $terms || is_wp_error($terms)) {
$terms = wp_get_object_terms($object_id, $term_taxonomy);
}
if (false !== $terms && !is_wp_error($terms)) {
$results = array_merge($results, $terms);
}
}
$cache = [];
$cache[$key] = $results;
wp_cache_set('generate_keywords', $cache, 'simple-tags');
} else {
if (isset($cache[$key])) {
return $cache[$key];
}
}
return $results;
}
return array();
}
public function taxopress_add_custom_url_field($term)
{
if (!is_object($term)) {
return;
}
$taxopress_custom_url = get_term_meta($term->term_id, 'taxopress_custom_url', true);
?>
|
|
|
count < $min_usage) {
continue;
}
}
if ($options && isset($options['autolink_usage_min'])) {
$autolink_min_usage = (int) $options['autolink_usage_min'];
if ($term->count < $autolink_min_usage) {
continue;
}
}
if (!$archivepage && $custom_urls_enabled) {
$taxopress_custom_url = isset($custom_urls[$term->term_id])
? $custom_urls[$term->term_id]
: '';
if (!empty($taxopress_custom_url)) {
self::$link_tags[$term->name] = esc_url($taxopress_custom_url);
}
} else {
//add primary term
if ($custom_urls_enabled) {
$taxopress_custom_url = isset($custom_urls[$term->term_id])
? $custom_urls[$term->term_id]
: '';
$primary_term_link = !empty($taxopress_custom_url) ? esc_url($taxopress_custom_url) : get_term_link($term, $term->taxonomy);
} else {
$primary_term_link = get_term_link($term, $term->taxonomy);
}
$add_terms = [];
// store the URL
self::$link_tags[$term->name] = $primary_term_link;
$add_terms[$term->name] = $primary_term_link;
// add term synonyms
if (is_array($options) && isset($options['synonyms_link']) && (int)$options['synonyms_link'] > 0) {
$term_synonyms = taxopress_get_term_synonyms($term->term_id);
if (!empty($term_synonyms)) {
foreach ($term_synonyms as $term_synonym) {
$add_terms[$term_synonym] = $primary_term_link;
}
}
}
// add linked term
$add_terms = taxopress_add_linked_term_options($add_terms, $term->name, $term->taxonomy, true);
foreach ($add_terms as $add_name => $add_term_link) {
//min character check
$min_char_pass = true;
if ($autolink_min_char > 0) {
$min_char_pass = strlen($add_name) >= $autolink_min_char ? true : false;
}
//max character check
$max_char_pass = true;
if ($autolink_max_char > 0) {
$max_char_pass = strlen($add_name) <= $autolink_max_char ? true : false;
}
if ($auto_link_min === 0 || $term->count >= $auto_link_min && $min_char_pass && $max_char_pass) {
self::$link_tags[$add_name] = esc_url($add_term_link);
}
}
}
}
return true;
}
private static function prepare_excluded_terms($exclude_terms)
{
$prepared_terms = [];
$exclude_terms = explode(',', (string) $exclude_terms);
foreach ($exclude_terms as $exclude_term) {
$exclude_term = trim(stripslashes($exclude_term));
if ('' === $exclude_term) {
continue;
}
$prepared_terms[] = $exclude_term;
}
return array_unique($prepared_terms);
}
/**
* Helper function to batch load term meta to reduce database queries
*/
private static function get_term_meta_batch($term_ids, $meta_key)
{
global $wpdb;
if (empty($term_ids)) {
return [];
}
// Sanitize term IDs
$term_ids = array_map('intval', $term_ids);
$term_ids_string = implode(',', $term_ids);
$results = $wpdb->get_results($wpdb->prepare(
"SELECT term_id, meta_value FROM {$wpdb->termmeta}
WHERE term_id IN ($term_ids_string) AND meta_key = %s",
$meta_key
));
$batch_data = [];
foreach ($results as $result) {
$batch_data[$result->term_id] = $result->meta_value;
}
return $batch_data;
}
private static function taxopress_get_title_attribute($search, $url, $options)
{
$title_attribute = isset($options['autolink_title_attribute'])
? $options['autolink_title_attribute']
: SimpleTags_Plugin::get_option_value('auto_link_title');
$term = get_term_by('name', $search, $options['taxonomy']);
if ($term) {
$custom_url = get_term_meta($term->term_id, 'taxopress_custom_url', true);
if (!empty($custom_url) && esc_url($custom_url) === $url) {
$title_attribute = isset($options['autolink_title_attribute_when_using_custom_url'])
? $options['autolink_title_attribute_when_using_custom_url']
: SimpleTags_Plugin::get_option_value('auto_link_title_custom_url');
}
}
return esc_attr(sprintf($title_attribute, $search));
}
/**
* Replace text by link, except HTML tag, and already text into link, use DOMdocument.
* https://stackoverflow.com/questions/4044812/regex-domdocument-match-and-replace-text-not-in-a-link
*
* @param string $content
* @param string $search
* @param string $replace
* @param string $case
* @param string $rel
*
* @return void
*/
private static function replace_by_links_dom(&$content, $search = '', $replace = '', $case = '', $rel = '', $options = false, $content_type = 'content')
{
global $post, $autolinked_contents;
if (!is_object($post) || !isset($post->ID) || empty($content)) {
return $content;
}
$dom = new DOMDocument();
if (!is_array($search)) {
$search_lists = [];
$search_lists[] = [
'term_name' => $search,
'term_link' => $replace,
'case' => $case,
'rel' => $rel,
'options' => $options,
'option_id' => 0,
'option_idxx' => 0,
'post_limit' => SimpleTags_Plugin::get_option_value('auto_link_max_by_post'),
'term_limit' => SimpleTags_Plugin::get_option_value('auto_link_max_by_tag'),
'type' => $content_type,
];
} else {
$search_lists = $search;
}
if (empty($search_lists)) {
return;
}
$content_type = $search_lists[0]['type'];
$content_key = $content_type . '_' . $post->ID;
if (!is_array($autolinked_contents)) {
$autolinked_contents = [];
}
if (isset($autolinked_contents[$content_key])) {
$content = $autolinked_contents[$content_key];
return $content;
}
// process blocks exclusion
if (is_array($search_lists[0]['options']) && !empty($search_lists[0]['options']['blocks_exclusion'])) {
$blocks_exclusion = $search_lists[0]['options']['blocks_exclusion'];
$process = function ($item) {
// Replace "core/" prefixes
$item = str_replace("core/", "", $item);
// Add "wp:" prefixes
return "wp:" . $item;
};
// Apply the processing function to each element of the array
$blocks_to_exclude = array_map($process, $blocks_exclusion);
// Escape special characters for regex pattern
$escaped_blocks = array_map(function ($block) {
return preg_quote($block, '/');
}, $blocks_to_exclude);
// Create a regex pattern from the block names
$pattern = '/(.*?)/s';
$content = preg_replace_callback(
$pattern,
function ($matches) {
// Return the matched block content wrapped in taxopressnotag
return '' . $matches[0] . '';
},
$content
);
}
// process shortcodes exclusion
if (is_array($search_lists[0]['options']) && !empty($search_lists[0]['options']['shortcodes_exclusion'])) {
$shortcodes_exclusion = $search_lists[0]['options']['shortcodes_exclusion'];
// Create a regex pattern to match any of the shortcodes with or without attributes
$pattern = '/\[(?:' . implode('|', $shortcodes_exclusion) . ')[^\]]*\]/';
// Define the replacement function to wrap matched shortcodes
$callback = function ($matches) {
return '' . $matches[0] . '';
};
// Apply the regex pattern to the content using the callback
$content = preg_replace_callback($pattern, $callback, $content);
}
//replace html entity with their entity code
foreach (taxopress_html_character_and_entity() as $enity => $code) {
$content = str_replace($enity, $code, $content);
}
// Replace HTML entities with placeholders
$content = preg_replace_callback('/(\d+);/', function ($matches) {
return 'STARTTAXOPRESSENTITY' . $matches[1] . 'TAXOPRESSENTITYEND';
}, $content);
//$content = str_replace('','|--|',$content);//https://github.com/TaxoPress/TaxoPress/issues/824
//$content = str_replace('&','&',$content); //https://github.com/TaxoPress/TaxoPress/issues/770*/
$content = 'starttaxopressrandom' . $content . 'endtaxopressrandom'; //we're having issue when content start with styles https://wordpress.org/support/topic/3-7-2-auto-link-case-not-working/#post-16665257
//$content = utf8_decode($content);
libxml_use_internal_errors(true);
// loadXml needs properly formatted documents, so it's better to use loadHtml, but it needs a hack to properly handle UTF-8 encoding
//$result = $dom->loadHtml(mb_convert_encoding($content, 'HTML-ENTITIES', "UTF-8"));
//$result = $dom->loadHtml(htmlspecialchars_decode($content, ENT_QUOTES | ENT_HTML5));
// Load the content as HTML without adding DOCTYPE and html/body tags
$content = '' . $content . '
';
$content = mb_encode_numericentity($content, [0x80, 0x10FFFF, 0, 0xFFFFF], 'UTF-8');
$result = $dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
if (false === $result) {
return;
}
$xpath = new DOMXPath($dom);
$j = 0;
$replaced_count = 0;
$replaced_tags_counts = [];
$option_limits = [];
$term_limits = [];
$option_remaining = [];
$option_tagged_counts = [];
$node_text = [];
foreach ($search_lists as $search_details) {
$search = $search_details['term_name'];
$replace = $search_details['term_link'];
$case = $search_details['case'];
$rel = $search_details['rel'];
$options = $search_details['options'];
$search = str_replace('&', 'taxopressamp', $search); // https://github.com/TaxoPress/TaxoPress/issues/1638
if (is_array($options)) {
$autolink_case = $options['autolink_case'];
$html_exclusion = $options['html_exclusion'];
$html_exclusion_customs = isset($options['html_exclusion_customs']) ? $options['html_exclusion_customs'] : [];
$exclude_class = $options['autolink_exclude_class'];
$title_attribute = $options['autolink_title_attribute'];
$title_attribute_custom_url = $options['autolink_title_attribute_when_using_custom_url'];
$link_class = isset($options['link_class']) ? taxopress_format_class($options['link_class']) : '';
} else {
$autolink_case = 'lowercase';
$html_exclusion = [];
$html_exclusion_customs = [];
$exclude_class = '';
$title_attribute = SimpleTags_Plugin::get_option_value('auto_link_title');
$title_attribute_custom_url = SimpleTags_Plugin::get_option_value('auto_link_title_custom_url');
$link_class = '';
}
$detail_id = $search_details['type'] . '_' . $search_details['option_id'];
if (!isset($option_limits[$detail_id])) {
$option_limits[$detail_id] = $search_details['post_limit'];
}
if (!isset($option_remaining[$detail_id])) {
$option_remaining[$detail_id] = $option_limits[$detail_id];
}
if (!isset($term_limits[$detail_id])) {
$term_limits[$detail_id] = min($search_details['term_limit'], $option_remaining[$detail_id]);
}
if (!isset($option_tagged_counts[$detail_id])) {
$option_tagged_counts[$detail_id] = 0;
}
$html_exclusion[] = 'taxopressnotag';
$html_exclusion[] = 'meta';
$html_exclusion[] = 'link';
$html_exclusion[] = 'head';
if (!empty($html_exclusion_customs)) {
$html_exclusion = array_merge($html_exclusion, $html_exclusion_customs);
}
//auto link exclusion
$exclusion = '[not(ancestor::a)][not(ancestor-or-self::a/@*)]';
if (count($html_exclusion) > 0) {
foreach ($html_exclusion as $exclude_ancestor) {
$exclusion .= '[not(ancestor::' . strtolower($exclude_ancestor) . ')][not(ancestor-or-self::' . strtolower($exclude_ancestor) . '/@*)]';
}
}
// Prepare exclude terms array
$excludes_class = explode(',', $exclude_class);
if (!empty($excludes_class)) {
$excludes_class = array_filter($excludes_class);
$excludes_class = array_unique($excludes_class);
if (count($excludes_class) > 0) {
foreach ($excludes_class as $idclass) {
if (substr(trim($idclass), 0, 1) === "#") {
$element_id = ltrim(trim($idclass), "#");
$exclusion .= "[not(ancestor::*[@id='$element_id'])]";
} else {
$element_class = ltrim(trim($idclass), ".");
$exclusion .= "[not(ancestor::*[@class='$element_class'])]";
}
}
}
}
foreach ($xpath->query('//text()' . $exclusion . '') as $node) {
// Exclude URLs from being replaced
if (preg_match('/(http|https):\/\/[^\s]+/i', $node->wholeText)) {
continue;
}
$url = array_key_exists($search, self::$link_tags) ? self::$link_tags[$search] : $replace;
$used_title = self::taxopress_get_title_attribute($search, $url, $options);
$replace = $url;
$substitute = '$search";
$link_openeing = '";
$link_closing = '';
$upperterm = strtoupper($search);
$lowerterm = strtolower($search);
if ($option_limits[$detail_id] > 0 && 0 >= $option_remaining[$detail_id]) {
break;
}
if ($term_limits[$detail_id] > 0 && array_key_exists($replace, $replaced_tags_counts) && $replaced_tags_counts[$replace] >= $term_limits[$detail_id]) {
continue;
}
if ($term_limits[$detail_id] > 0 && array_key_exists($replace, $replaced_tags_counts)) {
$same_usage_max = min($term_limits[$detail_id] - $replaced_tags_counts[$replace], $option_remaining[$detail_id]);
} else {
$same_usage_max = min($term_limits[$detail_id], $option_remaining[$detail_id]);
}
// Replace HTML entities with placeholders in term name too to match them in content
$search = preg_replace_callback('/(\d+);/', function ($matches) {
return 'STARTTAXOPRESSENTITY' . $matches[1] . 'TAXOPRESSENTITYEND';
}, $search);
$whole_words = isset($options['whole_words']) ? (int)$options['whole_words'] : 1;
if ($whole_words) {
$pattern = '/\b' . preg_quote($search, "/") . '\b/ui';
} else {
// Allow partial matches - no word boundary checks
$pattern = '/' . preg_quote($search, "/") . '/i';
}
//if ('i' === $case) {
if ($autolink_case === 'none') { // retain case
$replaced = preg_replace_callback($pattern, function ($matches) use ($link_openeing, $link_closing) {
return $link_openeing . htmlspecialchars($matches[0]) . $link_closing;
}, $node->wholeText, $same_usage_max, $rep_count);
} elseif ($autolink_case === 'uppercase') { // uppercase
$replaced = preg_replace_callback($pattern, function ($matches) use ($link_openeing, $upperterm, $link_closing) {
return $link_openeing . strtoupper($matches[0]) . $link_closing;
}, $node->wholeText, $same_usage_max, $rep_count);
} elseif ($autolink_case === 'termcase') { // termcase
$replaced = preg_replace_callback($pattern, function ($matches) use ($link_openeing, $search, $link_closing) {
return $link_openeing . $search . $link_closing;
}, $node->wholeText, $same_usage_max, $rep_count);
} else { // lowercase
$replaced = preg_replace_callback($pattern, function ($matches) use ($link_openeing, $lowerterm, $link_closing) {
return $link_openeing . strtolower($matches[0]) . $link_closing;
}, $node->wholeText, $same_usage_max, $rep_count);
}
if ($replaced && !empty(trim($replaced))) {
$j++;
if ($rep_count > 0) {
// TODO : Think about synonyms
if (array_key_exists($replace, $replaced_tags_counts)) {
$replaced_tags_counts[$replace] = $replaced_tags_counts[$replace] + $rep_count;
} else {
$replaced_tags_counts[$replace] = $rep_count;
}
$option_tagged_counts[$detail_id] = $option_tagged_counts[$detail_id] + $rep_count;
$option_remaining[$detail_id] = $option_limits[$detail_id] - $option_tagged_counts[$detail_id];
}
}
$newNode = $dom->createDocumentFragment();
$newNode->appendXML($replaced);
$node->parentNode->replaceChild($newNode, $node);
if ($option_remaining[$detail_id] === 0) {
break;
}
}
}
// Get the innerHTML of the root div, excluding the div itself
$content = '';
foreach ($dom->documentElement->childNodes as $node) {
$content .= $dom->saveHTML($node);
}
// Add back the starting ""
$content = str_replace('STARTTAXOPRESSENTITY', '', $content);
// Add back the ending ";"
$content = str_replace('TAXOPRESSENTITYEND', ';', $content);
// get only the body tag with its contents, then trim the body tag itself to get only the original content
//$content = mb_substr($dom->saveHTML($xpath->query('//body')->item(0)), 6, -7, "UTF-8");
$content = str_replace('|--|', '', $content); //https://github.com/TaxoPress/TaxoPress/issues/824
/**
* I commented the line below because of https://github.com/TaxoPress/TaxoPress/issues/2118
* In summary, when content contain < and > special character which are intentiona;, they're been
* changed to < > which is not needed
*/
//$content = str_replace('<', '<', $content);
//$content = str_replace('>', '>', $content);
foreach (taxopress_html_character_and_entity(true) as $enity => $code) {
$content = str_replace($enity, $code, $content);
}
$content = str_replace('& ;rsquo;', '’', $content);
$content = str_replace(['’', ' ’', '’', ' ’', '’', ' ’'], '\'', $content);
$content = str_replace('&', '&', $content); //https://github.com/TaxoPress/TaxoPress/issues/770
$content = str_replace(';amp;', ';', $content); //https://github.com/TaxoPress/TaxoPress/issues/810
$content = str_replace('%7C--%7C038;', '&', $content); //https://github.com/TaxoPress/TaxoPress/issues/1377
$content = str_replace('starttaxopressrandom', '', $content);
$content = str_replace('endtaxopressrandom', '', $content);
// replace added to skip certain elements
$content = str_replace('', '', $content);
$content = str_replace('', '', $content);
}
/**
* Replace text by link, except HTML tag, and already text into link, use PregEXP.
*
* @param string $content
* @param string $search
* @param string $replace
* @param string $case
* @param string $rel
*/
private static function replace_by_links_regexp(&$content, $search = '', $replace = '', $case = '', $rel = '', $options = false)
{
if ($options) {
$autolink_case = $options['autolink_case'];
$html_exclusion = $options['html_exclusion'];
$html_exclusion_customs = isset($options['html_exclusion_customs']) ? $options['html_exclusion_customs'] : [];
$exclude_class = $options['autolink_exclude_class'];
$title_attribute = $options['autolink_title_attribute'];
$title_attribute_custom_url = $options['autolink_title_attribute_when_using_custom_url'];
$same_usage_max = $options['autolink_same_usage_max'];
$max_by_post = $options['autolink_usage_max'];
$link_class = isset($options['link_class']) ? taxopress_format_class($options['link_class']) : '';
} else {
$autolink_case = 'lowercase';
$html_exclusion = [];
$html_exclusion_customs = [];
$exclude_class = '';
$title_attribute = SimpleTags_Plugin::get_option_value('auto_link_title');
$title_attribute_custom_url = SimpleTags_Plugin::get_option_value('auto_link_title_custom_url');
$same_usage_max = SimpleTags_Plugin::get_option_value('auto_link_max_by_tag');
$max_by_post = SimpleTags_Plugin::get_option_value('auto_link_max_by_post');
$link_class = '';
}
if (!empty($html_exclusion_customs)) {
$html_exclusion = array_merge($html_exclusion, $html_exclusion_customs);
}
$must_tokenize = true; // will perform basic tokenization
$tokens = null; // two kinds of tokens: markup and text
$j = 0;
$filtered = ''; // will filter text token by token
// Get URL and title first
$url = array_key_exists($search, self::$link_tags) ? self::$link_tags[$search] : $replace;
$used_title = self::taxopress_get_title_attribute($search, $url, $options);
$replace = $url;
// Determine word boundary pattern based on whole_words setting
$whole_words = isset($options['whole_words']) ? (int)$options['whole_words'] : 1;
if ($whole_words) {
// Use \b for strict whole word matching
$match = '/\b' . preg_quote($search, '/') . '\b/u' . $case;
$substitute = '$0";
} else {
// Allow partial matches - no word boundary checks
$match = '/(' . preg_quote($search, '/') . ')/u' . $case;
$substitute = '$1";
}
//$match = "/\b" . preg_quote($search, "/") . "\b/".$case;
//$substitute = '$0";
// for efficiency only tokenize if forced to do so
if ($must_tokenize) {
// this regexp is taken from PHP Markdown by Michel Fortin: http://www.michelf.com/projects/php-markdown/
$comment = '(?s:)|';
$processing_instruction = '(?s:<\?.*?\?>)|';
$tag = '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)';
$markup = $comment . $processing_instruction . $tag;
$flags = PREG_SPLIT_DELIM_CAPTURE;
$tokens = preg_split("{($markup)}", $content, -1, $flags);
$must_tokenize = false;
}
// there should always be at least one token, but check just in case
$anchor_level = 0;
if (isset($tokens) && is_array($tokens) && count($tokens) > 0) {
$i = 0;
$ancestor = '';
foreach ($tokens as $token) {
if (++$i % 2 && $token !== '') { // this token is (non-markup) text
$pass_check = true;
if (!empty(trim($ancestor))) {
//auto link exclusion
if (count($html_exclusion) > 0) {
foreach ($html_exclusion as $exclude_ancestor) {
if (taxopress_starts_with($ancestor, '<' . strtolower($exclude_ancestor) . '')) {
$pass_check = false;
break;
}
}
}
// Prepare exclude terms array
$excludes_class = explode(',', $exclude_class);
if (!empty($excludes_class)) {
$excludes_class = array_filter($excludes_class);
$excludes_class = array_unique($excludes_class);
if (count($excludes_class) > 0) {
foreach ($excludes_class as $idclass) {
if (substr(trim($idclass), 0, 1) === "#") {
$div_id = ltrim(trim($idclass), "#");
if (preg_match_all('/<[a-z \'"]*id="' . $div_id . '"/i', $ancestor, $matches) || preg_match_all('/<[a-z \'"]*id=\'' . $div_id . '\'/i', $ancestor, $matches)) {
$pass_check = false;
break;
}
} else {
$div_class = ltrim(trim($idclass), ".");
if (preg_match_all('/<[a-z ]*class="' . $div_class . '"/i', $ancestor, $matches) || preg_match_all('/<[a-z ]*class=\'' . $div_class . '\'/i', $ancestor, $matches)) {
$pass_check = false;
break;
}
}
}
}
}
}
if ($anchor_level === 0 && $pass_check) { // linkify if not inside anchor tags
if (preg_match($match, $token)) { // use preg_match for compatibility with PHP 4
$j++;
$remaining_usage = $max_by_post - self::$tagged_link_count;
if ($same_usage_max > $remaining_usage) {
$same_usage_max = $remaining_usage;
}
if ($same_usage_max > 0) { // Limit replacement at 1 by default, or options value !
$token = preg_replace($match, $substitute, $token, $same_usage_max, $rep_count); // only PHP 5 supports calling preg_replace with 5 arguments
self::$tagged_link_count = self::$tagged_link_count + $rep_count;
}
$must_tokenize = true; // re-tokenize next time around
}
}
} else { // this token is markup
if (preg_match("#<\s*a\s+[^>]*>#i", $token)) { // found
$ancestor = $token;
$anchor_level++;
} elseif (preg_match("#<\s*/\s*a\s*>#i", $token)) { // found
$anchor_level--;
} elseif (taxopress_starts_with($token, "")) {
$ancestor = '';
} else {
$ancestor = $token;
}
}
$filtered .= $token; // this token has now been filtered
}
$content = $filtered; // filtering completed for this link
}
}
/**
* Replace text by link to tag
*
* @param string $content
*
* @return string
*/
public static function taxopress_autolinks_the_content($content = '')
{
global $post;
// Some Elementor render flows may call filters with no global $post set.
// Try to recover a post object before bailing out.
if (!is_object($post)) {
$post = get_post();
}
if (!is_object($post) || is_admin()) {
return $content;
}
$post_tags = taxopress_get_autolink_data();
// User preference for this post?
$feature_status = self::get_post_feature_status($post->ID);
if (
'disabled' === $feature_status
|| ('default' === $feature_status && 1 !== (int) SimpleTags_Plugin::get_option_value('active_auto_links'))
) {
return $content;
}
$force_enabled = 'enabled' === $feature_status;
if (count($post_tags) > 0) {
$auto_link_replace = [];
foreach ($post_tags as $post_tag) {
// Get option
$embedded = (isset($post_tag['embedded']) && is_array($post_tag['embedded']) && count($post_tag['embedded']) > 0) ? $post_tag['embedded'] : false;
if (!$force_enabled && (!$embedded || !in_array($post->post_type, $embedded, true))) {
continue;
}
if ($post_tag['autolink_display'] === 'post_title') {
continue;
}
//reset tags just in case
self::$link_tags = [];
// Get currents tags if no exists
self::prepare_auto_link_tags($post_tag);
// Shuffle array
SimpleTags_Client::random_array(self::$link_tags);
// HTML Rel (tag/no-follow)
$rel = SimpleTags_Client::get_rel_attribut();
// only continue if the database actually returned any links
if (!isset(self::$link_tags) || !is_array(self::$link_tags) || empty(self::$link_tags)) {
$can_continue = false;
} else {
$can_continue = true;
}
if ($can_continue) {
// Case option ?
$case = (1 === (int) $post_tag['ignore_case']) ? 'i' : '';
$strpos_fnc = ('i' === $case) ? 'stripos' : 'strpos';
$excludes_terms = self::prepare_excluded_terms($post_tag['auto_link_exclude']);
$z = 0;
foreach ((array) self::$link_tags as $term_name => $term_link) {
$z++;
// Force string for tags "number"
$term_name = (string) $term_name;
// Exclude terms ? next...
if (taxopress_in_array_i($term_name, (array) $excludes_terms, true)) {
continue;
}
// Make a first test with PHP function, economize CPU with regexp
if (false === $strpos_fnc($post->post_content, $term_name)) {
continue;
}
$auto_link_replace[] = [
'term_name' => $term_name,
'term_link' => $term_link,
'case' => $case,
'rel' => $rel,
'options' => $post_tag,
'option_id' => $post_tag['ID'],
'post_limit' => $post_tag['autolink_usage_max'],
'term_limit' => $post_tag['autolink_same_usage_max'],
'type' => 'content',
];
if (!class_exists('DOMDocument') || !class_exists('DOMXPath')) {
self::replace_by_links_regexp($content, $term_name, $term_link, $case, $rel, $post_tag);
}
}
}
}
if (class_exists('DOMDocument') && class_exists('DOMXPath')) {
self::replace_by_links_dom($content, $auto_link_replace);
}
}
return $content;
}
/**
* Replace text by link to tag
*
* @param string $title
*
* @return string
*/
public static function taxopress_autolinks_the_title($title = '')
{
global $post;
if (!is_object($post) || is_admin()) {
return $title;
}
$post_tags = taxopress_get_autolink_data();
// User preference for this post?
$feature_status = self::get_post_feature_status($post->ID);
if (
'disabled' === $feature_status
|| ('default' === $feature_status && 1 !== (int) SimpleTags_Plugin::get_option_value('active_auto_links'))
) {
return $title;
}
$force_enabled = 'enabled' === $feature_status;
if (count($post_tags) > 0) {
foreach ($post_tags as $post_tag) {
// Get option
$embedded = (isset($post_tag['embedded']) && is_array($post_tag['embedded']) && count($post_tag['embedded']) > 0) ? $post_tag['embedded'] : false;
if (!$force_enabled && (!$embedded || !in_array($post->post_type, $embedded, true))) {
continue;
}
if ($post_tag['autolink_display'] === 'post_content') {
continue;
}
//reset tags just in case
self::$link_tags = [];
// Get currents tags if no exists
self::prepare_auto_link_tags($post_tag);
// Shuffle array
SimpleTags_Client::random_array(self::$link_tags);
// HTML Rel (tag/no-follow)
$rel = SimpleTags_Client::get_rel_attribut();
// only continue if the database actually returned any links
if (!isset(self::$link_tags) || !is_array(self::$link_tags) || empty(self::$link_tags)) {
$can_continue = false;
} else {
$can_continue = true;
}
if ($can_continue) {
// Case option ?
$case = (1 === (int) $post_tag['ignore_case']) ? 'i' : '';
$strpos_fnc = ('i' === $case) ? 'stripos' : 'strpos';
$excludes_terms = self::prepare_excluded_terms($post_tag['auto_link_exclude']);
$z = 0;
$auto_link_replace = [];
foreach ((array) self::$link_tags as $term_name => $term_link) {
$z++;
// Force string for tags "number"
$term_name = (string) $term_name;
// Exclude terms ? next...
if (taxopress_in_array_i($term_name, (array) $excludes_terms, true)) {
continue;
}
// Make a first test with PHP function, economize CPU with regexp
if (false === $strpos_fnc($title, $term_name)) {
continue;
}
$auto_link_replace[] = [
'term_name' => $term_name,
'term_link' => $term_link,
'case' => $case,
'rel' => $rel,
'options' => $post_tag,
'option_id' => $post_tag['ID'],
'post_limit' => $post_tag['autolink_usage_max'],
'term_limit' => $post_tag['autolink_same_usage_max'],
'type' => 'content',
];
if (!class_exists('DOMDocument') || !class_exists('DOMXPath')) {
self::replace_by_links_regexp($title, $term_name, $term_link, $case, $rel, $post_tag);
}
}
if (class_exists('DOMDocument') && class_exists('DOMXPath')) {
self::replace_by_links_dom($title, $auto_link_replace);
}
}
}
}
return $title;
}
private static function get_post_feature_status($post_id)
{
$status = get_post_meta($post_id, '_taxopress_autolinks_status', true);
if (in_array($status, ['default', 'enabled', 'disabled'], true)) {
return $status;
}
if (get_post_meta($post_id, '_exclude_autolinks', true)) {
return 'disabled';
}
return 'default';
}
}