card metadata.
*/
public static function get_keyword_index() {
$cached_index = get_transient( self::CACHE_KEY );
if ( false !== $cached_index && is_array( $cached_index ) ) {
return $cached_index;
}
$index = array();
$boxes = get_posts(
array(
'post_type' => 'affistyle_box',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'no_found_rows' => true,
)
);
if ( empty( $boxes ) || ! is_array( $boxes ) ) {
set_transient( self::CACHE_KEY, $index, 24 * HOUR_IN_SECONDS );
return $index;
}
foreach ( $boxes as $box_id ) {
$box_id = absint( $box_id );
$keywords = get_post_meta( $box_id, '_affistyle_keywords', true );
if ( empty( $keywords ) ) {
continue;
}
// Support both array and comma-separated string formats.
if ( is_string( $keywords ) ) {
$keywords = explode( ',', $keywords );
}
if ( ! is_array( $keywords ) ) {
continue;
}
$title = get_post_meta( $box_id, '_affistyle_title_1', true );
$price = get_post_meta( $box_id, '_affistyle_price_1', true );
$rating = get_post_meta( $box_id, '_affistyle_rating_score', true );
$image = get_post_meta( $box_id, '_affistyle_image_1', true );
$button_text = get_post_meta( $box_id, '_affistyle_button_text', true );
if ( empty( $title ) ) {
$title = get_the_title( $box_id );
}
if ( empty( $image ) ) {
$image = get_post_meta( $box_id, '_affistyle_image_url', true );
}
if ( empty( $button_text ) ) {
$button_text = __( 'Check Deal', 'affistyle' );
}
$cloaked_url = class_exists( 'AffiStyle_Cloaking' )
? AffiStyle_Cloaking::get_cloaked_url( $box_id, 1 )
: get_permalink( $box_id );
// Append tracking source to the destination.
$cloaked_url = add_query_arg(
array(
'affistyle_source' => 'keyword_auto_link',
'aff_src' => 'keyword_auto_link',
),
$cloaked_url
);
$card_data = array(
'box_id' => $box_id,
'title' => $title,
'price' => $price,
'rating' => $rating,
'image' => $image,
'button_text' => $button_text,
'cloaked_url' => $cloaked_url,
);
foreach ( $keywords as $raw_kw ) {
$clean_kw = trim( (string) $raw_kw );
if ( '' === $clean_kw ) {
continue;
}
$normalized_kw = function_exists( 'mb_strtolower' )
? mb_strtolower( $clean_kw, 'UTF-8' )
: strtolower( $clean_kw );
// Avoid overwriting a more specific keyword if already set.
if ( ! isset( $index[ $normalized_kw ] ) ) {
$index[ $normalized_kw ] = $card_data;
}
}
}
set_transient( self::CACHE_KEY, $index, 24 * HOUR_IN_SECONDS );
return $index;
}
/**
* Main the_content filter callback. Parses and injects auto-links safely.
*
* @param string $content Post content HTML.
* @return string Filtered post content.
*/
public function filter_content( $content ) {
// Guard clause 1: Verify string input.
if ( empty( $content ) || ! is_string( $content ) ) {
return $content;
}
// Guard clause 2: Skip in admin, feeds, XML-RPC, and REST API requests.
if ( is_admin() || is_feed() || wp_is_json_request() ) {
return $content;
}
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return $content;
}
// Guard clause 3: Allow external filters to bypass auto-linking (e.g. Pro post opt-out).
$can_process = apply_filters( 'affistyle_autolink_can_process', true, $content, get_the_ID() );
if ( true !== $can_process ) {
return $content;
}
// Retrieve active keyword dictionary.
$keyword_index = static::get_keyword_index();
if ( empty( $keyword_index ) || ! is_array( $keyword_index ) ) {
return $content;
}
// Sort keywords by string length in descending order to avoid prefix collision.
uksort(
$keyword_index,
array( $this, 'sort_by_length_desc' )
);
// Link limits.
$max_total_links = apply_filters( 'affistyle_autolink_max_links_per_post', 2, get_the_ID() );
$max_per_keyword = apply_filters( 'affistyle_autolink_max_per_keyword', 1, get_the_ID() );
$total_links_done = 0;
$keyword_counts = array();
// Compile the master alternation regex pattern.
$escaped_keywords = array();
foreach ( array_keys( $keyword_index ) as $kw ) {
$escaped_keywords[] = preg_quote( $kw, '/' );
}
$master_pattern = '/(?]+>)/u', $content, -1, PREG_SPLIT_DELIM_CAPTURE );
if ( empty( $tokens ) || ! is_array( $tokens ) ) {
return $content;
}
$output_buffer = '';
$tag_stack = array();
// List of HTML tags inside which text must never be linked.
$protected_tags = array(
'a',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'script',
'style',
'code',
'pre',
'kbd',
'button',
'input',
'select',
'textarea',
'svg',
'canvas',
);
foreach ( $tokens as $token_idx => $token ) {
// Fast exit: If maximum links reached for this post, append the remaining tokens and stop.
if ( $total_links_done >= $max_total_links ) {
$output_buffer .= implode( '', array_slice( $tokens, $token_idx ) );
break;
}
// Check if token is an HTML tag.
if ( isset( $token[0] ) && '<' === $token[0] && '>' === substr( $token, -1 ) ) {
// 1. Closing tag:
if ( preg_match( '#^([a-z0-9_-]+)>#i', $token, $close_match ) ) {
$closing_tag = strtolower( $close_match[1] );
// Pop from tag stack.
$pos = array_search( $closing_tag, $tag_stack, true );
if ( false !== $pos ) {
array_splice( $tag_stack, $pos, 1 );
}
} elseif ( ! preg_match( '#/>$#', $token ) ) {
// 2. Opening tag (excluding self-closing tags like ,
,