1, 'small' => 3, 'business' => 5, 'agency' => 10, 'pro agency' => 50, 'pro_agency' => 50, ] ); function lfgail_is_licensed() { return true; } function lfgail_get_license() { return [ 'key' => '', 'status' => 'active', 'plan' => 'Free', 'site_limit' => 0, 'sites_used' => 0, 'expires' => '', 'checked_at' => 0 ]; } function lfgail_activate_license( $key ) { return [ 'success' => false, 'message' => 'License activation is not available in the free version.' ]; } function lfgail_deactivate_license() { return [ 'success' => false, 'message' => 'Not applicable.' ]; } function lfgail_plan_limit_from_name( $n ) { return 1; } function lfgail_default_settings() { return [ 'max_links_per_post' => 6, 'max_links_per_kw' => 1, 'link_target' => '_self', 'min_keyword_length' => 4, 'min_words_between' => 100, 'skip_first_n_paras' => 1, 'min_anchor_words' => 1, 'max_anchor_words' => 3, 'skip_headings' => 1, 'extraction_mode' => 'smart', 'claude_api_key' => '', 'blacklist' => implode( "\n", [ 'the','and','but','for','nor','yet','so','this','that','these','those','your','their','there', 'they','them','you','our','its','his','her','with','from','into','over','after','before','about', 'above','below','under','between','through','during','have','make','know','take','come','look','will', 'need','want','find','give','tell','keep','like','also','more','when','where','which','what','just', 'guide','tips','ways','things','people','place','places','time','year','years','world','life','long','days', 'area','part','parts','side','type','types','kind','site','sites','page','post','article','list', 'most','best','first','last','every','each','only','some','even','than','then','such','well','right', 'great','good','real','true','full','complete','ultimate','essential','perfect','top','main', 'review','reviews','versus','compared','comparison','pros','cons','overview','introduction', 'everything','something','nothing','anything', ] ), ]; } function lfgail_get_settings() { $saved = get_option( LFGAIL_OPTION_KEY, [] ); if ( ! is_array( $saved ) ) { $saved = []; } return wp_parse_args( $saved, lfgail_default_settings() ); } function lfgail_extract_keywords_smart( $title, $blacklist, $min_len ) { $keywords = []; $colon_pos = strpos( $title, ':' ); if ( $colon_pos !== false ) { $before = trim( substr( $title, 0, $colon_pos ) ); $after = trim( substr( $title, $colon_pos + 1 ) ); } else { $before = $title; $after = ''; } $tokenise = function( $str ) { $str = preg_replace( '/[^a-z0-9\s]/i', ' ', $str ); return array_values( array_filter( preg_split( '/\s+/', strtolower( trim( $str ) ) ) ) ); }; $is_ok = function( $word ) use ( $blacklist, $min_len ) { return strlen( $word ) >= $min_len && ! in_array( $word, $blacklist, true ); }; $bw = $tokenise( $before ); $bw_clean = array_values( array_filter( $bw, $is_ok ) ); if ( count( $bw_clean ) >= 2 && count( $bw_clean ) <= 5 ) { $keywords[] = implode( ' ', $bw_clean ); } for ( $i = 0; $i < count( $bw ) - 2; $i++ ) { $w1 = $bw[ $i ]; $w2 = $bw[ $i + 1 ]; $w3 = $bw[ $i + 2 ]; if ( $is_ok( $w1 ) && $is_ok( $w2 ) && $is_ok( $w3 ) ) { $keywords[] = "$w1 $w2 $w3"; } } for ( $i = 0; $i < count( $bw ) - 1; $i++ ) { $w1 = $bw[ $i ]; $w2 = $bw[ $i + 1 ]; if ( $is_ok( $w1 ) && $is_ok( $w2 ) ) { $keywords[] = "$w1 $w2"; } } foreach ( $bw as $w ) { if ( $is_ok( $w ) ) { $keywords[] = $w; } } if ( $after !== '' ) { $aw = $tokenise( $after ); for ( $i = 0; $i < count( $aw ) - 2; $i++ ) { $w1 = $aw[ $i ]; $w2 = $aw[ $i + 1 ]; $w3 = $aw[ $i + 2 ]; if ( $is_ok( $w1 ) && $is_ok( $w2 ) && $is_ok( $w3 ) ) { $keywords[] = "$w1 $w2 $w3"; } } for ( $i = 0; $i < count( $aw ) - 1; $i++ ) { $w1 = $aw[ $i ]; $w2 = $aw[ $i + 1 ]; if ( $is_ok( $w1 ) && $is_ok( $w2 ) ) { $keywords[] = "$w1 $w2"; } } } return array_unique( $keywords ); } function lfgail_extract_keywords_claude( $posts_data, $api_key ) { $titles_json = wp_json_encode( array_map( fn( $p ) => [ 'id' => $p['post_id'], 'title' => $p['title'] ], $posts_data ), JSON_UNESCAPED_UNICODE ); $prompt = "You are an SEO expert. For each blog post title below, extract 3-6 keyword phrases that would make excellent internal link anchor texts. Rules:\n" . "- Include the most specific and meaningful phrases from the title\n" . "- Include 1-word, 2-word, and 3-word variations\n" . "- Do NOT include stopwords, filler words, or generic adjectives\n" . "- Output ONLY valid JSON: an array of objects with \"id\" (number) and \"keywords\" (array of strings)\n" . "- All keywords must be lowercase\n\n" . "Titles:\n" . $titles_json; $response = wp_remote_post( 'https://api.anthropic.com/v1/messages', [ 'timeout' => 60, 'headers' => [ 'Content-Type' => 'application/json', 'x-api-key' => $api_key, 'anthropic-version' => '2023-06-01', ], 'body' => wp_json_encode( [ 'model' => 'claude-haiku-4-5-20251001', 'max_tokens' => 4096, 'messages' => [ [ 'role' => 'user', 'content' => $prompt ] ], ] ), ] ); if ( is_wp_error( $response ) ) { return null; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $text = $body['content'][0]['text'] ?? ''; $text = preg_replace( '/^```(?:json)?\s*/m', '', $text ); $text = preg_replace( '/\s*```$/m', '', $text ); $parsed = json_decode( trim( $text ), true ); if ( ! is_array( $parsed ) ) { return null; } $result = []; foreach ( $posts_data as $p ) { $result[ $p['post_id'] ] = $p; } foreach ( $parsed as $item ) { $id = (int) ( $item['id'] ?? 0 ); if ( isset( $result[ $id ] ) && ! empty( $item['keywords'] ) ) { $result[ $id ]['claude_keywords'] = array_map( 'strtolower', (array) $item['keywords'] ); } } return $result; } function lfgail_build_index( $force = false ) { $settings = lfgail_get_settings(); if ( ! $force ) { $cached = get_option( LFGAIL_INDEX_KEY, null ); if ( $cached !== null ) { return $cached; } } $blacklist = lfgail_get_blacklist_array( $settings['blacklist'] ); $min_len = (int) $settings['min_keyword_length']; $mode = $settings['extraction_mode']; $api_key = trim( (string) ( $settings['claude_api_key'] ?? '' ) ); $post_ids = get_posts( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ] ); $posts_data = []; foreach ( $post_ids as $id ) { $posts_data[] = [ 'post_id' => $id, 'title' => get_the_title( $id ), 'url' => get_permalink( $id ), ]; } $index = []; if ( $mode === 'claude' && ! empty( $api_key ) ) { $batches = array_chunk( $posts_data, 50 ); $claude_data = []; foreach ( $batches as $batch ) { $result = lfgail_extract_keywords_claude( $batch, $api_key ); if ( $result ) { $claude_data = array_merge( $claude_data, $result ); } } foreach ( $claude_data as $data ) { $keywords = $data['claude_keywords'] ?? lfgail_extract_keywords_smart( $data['title'], $blacklist, $min_len ); foreach ( $keywords as $kw ) { $kw = trim( strtolower( $kw ) ); if ( $kw !== '' && ! isset( $index[ $kw ] ) ) { $index[ $kw ] = [ 'post_id' => $data['post_id'], 'url' => $data['url'], 'title' => $data['title'], ]; } } } } else { foreach ( $posts_data as $data ) { $keywords = lfgail_extract_keywords_smart( $data['title'], $blacklist, $min_len ); foreach ( $keywords as $kw ) { $kw = trim( strtolower( $kw ) ); if ( $kw !== '' && ! isset( $index[ $kw ] ) ) { $index[ $kw ] = [ 'post_id' => $data['post_id'], 'url' => $data['url'], 'title' => $data['title'], ]; } } } } $min_w = max( 1, (int) $settings['min_anchor_words'] ); $max_w = max( $min_w, (int) $settings['max_anchor_words'] ); foreach ( array_keys( $index ) as $kw ) { $wc = count( explode( ' ', trim( $kw ) ) ); if ( $wc < $min_w || $wc > $max_w ) { unset( $index[ $kw ] ); } } uksort( $index, fn( $a, $b ) => strlen( $b ) - strlen( $a ) ); update_option( LFGAIL_INDEX_KEY, $index, false ); return $index; } function lfgail_get_blacklist_array( $raw ) { $lines = preg_split( '/[\r\n,]+/', strtolower( (string) $raw ) ); return array_values( array_filter( array_map( 'trim', $lines ) ) ); } add_action( 'save_post', 'lfgail_invalidate_index' ); add_action( 'delete_post', 'lfgail_invalidate_index' ); function lfgail_invalidate_index( $post_id ) { if ( wp_is_post_revision( $post_id ) || get_post_type( $post_id ) !== 'post' ) { return; } delete_option( LFGAIL_INDEX_KEY ); } function lfgail_run_daily_rebuild() { lfgail_build_index( true ); } add_action( 'lfgail_daily_rebuild', 'lfgail_run_daily_rebuild' ); if ( ! wp_next_scheduled( 'lfgail_daily_rebuild' ) ) { wp_schedule_event( time(), 'daily', 'lfgail_daily_rebuild' ); } add_filter( 'the_content', 'lfgail_inject_links', 20 ); function lfgail_inject_links( $content ) { if ( ! is_singular( 'post' ) ) { return $content; } $current_id = get_the_ID(); $settings = lfgail_get_settings(); $index = lfgail_build_index(); if ( empty( $index ) ) { return $content; } $target = $settings['link_target'] === '_blank' ? ' target="_blank" rel="noopener"' : ''; $max_links = (int) $settings['max_links_per_post']; $max_per_kw = (int) $settings['max_links_per_kw']; $min_words_gap = (int) $settings['min_words_between']; $skip_paras = (int) $settings['skip_first_n_paras']; $skip_headings = (bool) $settings['skip_headings']; $segments = preg_split( '/(].*?<\/p>)/is', $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $para_index = 0; $links_added = 0; $used_keywords = []; $used_urls = []; $words_since_last_link = $min_words_gap + 1; $result = []; $inside_heading = false; foreach ( $segments as $segment ) { $is_para = (bool) preg_match( '/^]/i', $segment ); if ( $is_para ) { $para_index++; if ( $para_index <= $skip_paras || $links_added >= $max_links ) { $words_since_last_link += lfgail_count_words( $segment ); $result[] = $segment; continue; } } $parts = preg_split( '/(<[^>]+>)/s', $segment, -1, PREG_SPLIT_DELIM_CAPTURE ); $inside_a = false; $seg_out = []; foreach ( $parts as $part ) { if ( preg_match( '/^<[^>]+>$/s', $part ) ) { if ( preg_match( '/^]/i', $part ) ) $inside_a = true; if ( preg_match( '/^<\/a>/i', $part ) ) $inside_a = false; if ( $skip_headings ) { if ( preg_match( '/^]/i', $part ) ) $inside_heading = true; if ( preg_match( '/^<\/h[1-6]>/i', $part ) ) $inside_heading = false; } $seg_out[] = $part; continue; } if ( $inside_a || $inside_heading || $links_added >= $max_links ) { $words_since_last_link += lfgail_count_words( $part ); $seg_out[] = $part; continue; } foreach ( $index as $keyword => $meta ) { if ( $links_added >= $max_links ) break; if ( (int) $meta['post_id'] === (int) $current_id ) continue; if ( ( $used_keywords[ $keyword ] ?? 0 ) >= $max_per_kw ) continue; if ( ( $used_urls[ $meta['url'] ] ?? 0 ) >= 2 ) continue; if ( $words_since_last_link < $min_words_gap ) continue; $pattern = '/(?$1'; $new_part = preg_replace( $pattern, $replacement, $part, 1 ); if ( $new_part !== $part ) { preg_match( $pattern, $part, $m, PREG_OFFSET_CAPTURE ); $words_since_last_link += lfgail_count_words( substr( $part, 0, $m[0][1] ) ); $part = wp_kses_post( $new_part ); $links_added++; $used_keywords[ $keyword ] = ( $used_keywords[ $keyword ] ?? 0 ) + 1; $used_urls[ $meta['url'] ] = ( $used_urls[ $meta['url'] ] ?? 0 ) + 1; $words_since_last_link = 0; preg_match( '/<\/a>(.*)/s', $part, $after ); if ( ! empty( $after[1] ) ) { $words_since_last_link += lfgail_count_words( wp_strip_all_tags( $after[1] ) ); } break; } } } if ( strpos( $part, '' ) === false ) { $words_since_last_link += lfgail_count_words( $part ); } $seg_out[] = $part; } $result[] = implode( '', $seg_out ); } return implode( '', $result ); } function lfgail_count_words( $text ) { $clean = trim( preg_replace( '/\s+/', ' ', strip_tags( $text ) ) ); return $clean === '' ? 0 : count( explode( ' ', $clean ) ); }