#i', // '', // $html // ); // $html = preg_replace( // '##i', // '', // $html // ); // return $html; // } ); // } // add_action( 'wp_head', 'ak_opta_child_force_og_image_in_head', 1 ); /** * Confirmed from opta-wp/style.css — the preloader uses class * '.doc-loader' (full-screen, fixed, z-index 99999, white background). * Override is now baked into style.css; no inline injection needed. */ /* ═══════════════════════════════════════════════════════════════ * AEO (Answer Engine Optimization) — Schema Injection * ─────────────────────────────────────────────────────────────── * Auto-injects structured data (JSON-LD) on blog posts and pages * so AI answer engines (ChatGPT, Perplexity, Google AI Overviews) * can extract, cite, and surface your content. * * Three schemas are injected: * 1. BlogPosting — every single post * 2. FAQPage — posts containing FAQ sections (auto-detected) * 3. BreadcrumbList — all posts and non-front pages * * IMPORTANT: If AIOSEO is also outputting Article schema, disable * it first: AIOSEO → Search Appearance → Content Types → Posts → * Schema Markup → set to "None" to avoid duplicates. * ═══════════════════════════════════════════════════════════════ */ // ── 1. BlogPosting Schema (all single posts) ────────────────── add_action( 'wp_head', 'ak_inject_blogposting_schema', 5 ); function ak_inject_blogposting_schema() { if ( ! is_single() ) return; global $post; $pub = get_the_date( 'c', $post ); $mod = get_the_modified_date( 'c', $post ); $excerpt = wp_strip_all_tags( get_the_excerpt( $post ) ); $thumb = get_the_post_thumbnail_url( $post, 'full' ); $cats = wp_get_post_categories( $post->ID, array( 'fields' => 'names' ) ); $tags = wp_get_post_tags( $post->ID, array( 'fields' => 'names' ) ); $word_count = str_word_count( wp_strip_all_tags( $post->post_content ) ); // Fallback image if no featured image set if ( ! $thumb ) { $thumb = 'https://abhilashkrishnan.com/assets/abhilash-krishan-real.png'; } $schema = array( '@context' => 'https://schema.org', '@type' => 'BlogPosting', 'mainEntityOfPage' => array( '@type' => 'WebPage', '@id' => get_permalink( $post ), ), 'headline' => get_the_title( $post ), 'description' => $excerpt, 'image' => $thumb, 'datePublished' => $pub, 'dateModified' => $mod, 'wordCount' => $word_count, 'author' => array( '@type' => 'Person', 'name' => 'Abhilash Krishnan', 'url' => 'https://abhilashkrishnan.com', 'jobTitle' => 'Creative Director & Thought Leader', 'sameAs' => array( 'https://www.linkedin.com/in/abhilashkrishnan/', 'https://twitter.com/freezebyte', ), ), 'publisher' => array( '@type' => 'Person', 'name' => 'Abhilash Krishnan', 'url' => 'https://abhilashkrishnan.com', 'logo' => array( '@type' => 'ImageObject', 'url' => 'https://abhilashkrishnan.com/assets/abhilash-krishnan-logo.svg', ), ), 'isPartOf' => array( '@type' => 'Blog', 'name' => 'Thoughts', 'url' => 'https://abhilashkrishnan.com/thoughts/', ), ); if ( ! empty( $cats ) ) { $schema['articleSection'] = $cats; } if ( ! empty( $tags ) ) { $schema['keywords'] = $tags; } echo "\n\n"; echo '\n"; } // ── 2. FAQPage Schema (posts with FAQ sections) ────────────── // Auto-detects two patterns: // a) SEOBot posts with data-faq-q attributes on H3s // b) Any H2 containing "FAQ" followed by H3 + paragraph pairs add_action( 'wp_head', 'ak_inject_faqpage_schema', 6 ); function ak_inject_faqpage_schema() { if ( ! is_single() ) return; global $post; $content = $post->post_content; $faqs = array(); // Strategy 1: data-faq-q pattern (SEOBot posts) if ( preg_match_all( '/
.*?<\/p>\s*)+)/si', $content, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $m ) { $question = wp_strip_all_tags( $m[1] ); $answer = wp_strip_all_tags( $m[2] ); $answer = trim( preg_replace( '/\s+/', ' ', $answer ) ); if ( $question && $answer ) { $faqs[] = array( '@type' => 'Question', 'name' => $question, 'acceptedAnswer' => array( '@type' => 'Answer', 'text' => $answer, ), ); } } } // Strategy 2: H2 "FAQs"/"FAQ" followed by H3 + paragraph pairs if ( empty( $faqs ) && preg_match( '/
.*?<\/p>\s*)+)+)/si', $content, $faq_block ) ) { if ( preg_match_all( '/
.*?<\/p>\s*)+)/si',
$faq_block[1],
$matches,
PREG_SET_ORDER
) ) {
foreach ( $matches as $m ) {
$question = wp_strip_all_tags( $m[1] );
$answer = wp_strip_all_tags( $m[2] );
$answer = trim( preg_replace( '/\s+/', ' ', $answer ) );
if ( $question && $answer ) {
$faqs[] = array(
'@type' => 'Question',
'name' => $question,
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $answer,
),
);
}
}
}
}
if ( empty( $faqs ) ) return;
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $faqs,
);
echo "\n\n";
echo '\n";
}
// ── 3. BreadcrumbList Schema ──────────────────────────────────
// DISABLED: AIOSEO Free auto-generates BreadcrumbList schema and
// has no toggle to turn it off. Letting AIOSEO handle this avoids
// duplicate breadcrumb entries in Rich Results Test.
// ── 4. Fix og:image — use featured image per post ────────────
// AIOSEO Free doesn't let you change the default image source,
// so it falls back to your profile photo on every post. This
// filter overrides og:image with the post's featured image.
add_filter( 'aioseo_opengraph_tags', 'ak_fix_og_image', 20 );
function ak_fix_og_image( $tags ) {
if ( ! is_single() ) return $tags;
global $post;
$thumb = get_the_post_thumbnail_url( $post, 'full' );
if ( $thumb ) {
$tags['og:image'] = $thumb;
$tags['og:image:secure_url'] = $thumb;
// Get image dimensions
$thumb_id = get_post_thumbnail_id( $post );
if ( $thumb_id ) {
$meta = wp_get_attachment_metadata( $thumb_id );
if ( $meta && isset( $meta['width'] ) ) {
$tags['og:image:width'] = $meta['width'];
$tags['og:image:height'] = $meta['height'];
}
}
}
return $tags;
}
// Also fix Twitter card image
add_filter( 'aioseo_twitter_tags', 'ak_fix_twitter_image', 20 );
function ak_fix_twitter_image( $tags ) {
if ( ! is_single() ) return $tags;
global $post;
$thumb = get_the_post_thumbnail_url( $post, 'full' );
if ( $thumb ) {
$tags['twitter:image'] = $thumb;
}
return $tags;
}