<?php /** * citAEOtion Child Theme — functions.php * * Enqueues parent theme stylesheet, then conditionally enqueues child custom.css * if it exists. custom.css is managed by citAEOtion Options → Custom CSS admin tab. * * @package citAEOtion_Theme_Child */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Enqueue parent theme stylesheet + child custom.css (if present). * * Priority 100 ensures we run after most parent theme enqueues so child CSS * cascades correctly. Dependency on the parent handle ensures load order. * * filemtime() on custom.css = automatic cache bust on every save through the * admin Custom CSS field. No manual version bumping required. */ function citaeotion_child_enqueue_styles() { // Parent theme stylesheet. $parent_version = wp_get_theme( 'citaeotion-theme' )->get( 'Version' ); wp_enqueue_style( 'fcs-aeo-parent-style', get_template_directory_uri() . '/style.css', array(), $parent_version ); // Child custom.css — only enqueue if file exists. Cache busted by filemtime(). $custom_css_path = get_stylesheet_directory() . '/custom.css'; if ( file_exists( $custom_css_path ) ) { wp_enqueue_style( 'fcs-aeo-child-custom-css', get_stylesheet_directory_uri() . '/custom.css', array( 'fcs-aeo-parent-style' ), (string) filemtime( $custom_css_path ) ); } } add_action( 'wp_enqueue_scripts', 'citaeotion_child_enqueue_styles', 100 ); // [citaeotion_posts] — recent posts as dark cards for the Blog page. function citaeotion_posts_shortcode( $atts ) { $atts = shortcode_atts( array( 'count' => 12 ), $atts ); $q = new WP_Query( array( 'post_type' => 'post', 'posts_per_page' => (int) $atts['count'], 'post_status' => 'publish', 'ignore_sticky_posts' => true, ) ); if ( ! $q->have_posts() ) { return '<p>Fresh posts are on the way.</p>'; } $out = '<div class="cae-posts">'; while ( $q->have_posts() ) { $q->the_post(); $out .= '<a class="cae-post" href="' . esc_url( get_permalink() ) . '">'; if ( has_post_thumbnail() ) { $out .= '<span class="cae-post-img">' . get_the_post_thumbnail( get_the_ID(), 'medium_large', array( 'loading' => 'lazy' ) ) . '</span>'; } $out .= '<span class="cae-post-body">'; $out .= '<span class="cae-post-date">' . esc_html( get_the_date() ) . '</span>'; $out .= '<span class="cae-post-title">' . esc_html( get_the_title() ) . '</span>'; $out .= '<span class="cae-post-ex">' . esc_html( wp_trim_words( get_the_excerpt(), 22 ) ) . '</span>'; $out .= '<span class="cae-post-more">Read more &rarr;</span>'; $out .= '</span></a>'; } $out .= '</div>'; wp_reset_postdata(); return $out; } add_shortcode( 'citaeotion_posts', 'citaeotion_posts_shortcode' ); /* -------------------------------------------------------------------------- * AGENT-READINESS: .md URL routes (child-level; parent theme untouched). * * Added 2026-07-20 by AgentGauge remediation pass. The parent theme engine * already negotiates "Accept: text/markdown" on ordinary page URLs (verified * live: Vary: Accept, X-Markdown-Tokens, Link rel=canonical all present) — * that handler is NOT duplicated here. The one real, verified gap was that * literal *.md URLs (the same paths the parent engine itself advertises via * `Link: <...>; rel="alternate"; type="text/markdown"` on every page) 404'd * instead of resolving. This section closes only that gap: * * - /index.md -> site index as markdown * - /{slug}.md -> that published page/post/faq as markdown * - anything else ending .md with no match -> falls through to normal 404 * * Ported from the itsmylinks-deploy child theme's iml_agent_* functions, * prefixed cae_agent_ for this site, dropping the content-negotiation * handler (iml_agent_content_negotiation) since the parent already owns * that behavior here and a second handler risks a double response. * -------------------------------------------------------------------------- */ /** * in wp_head. Live homepage HTML was checked before * adding this (2026-07-20) and had no author meta tag — the site-wide head * schema carries a Person node but not this tag, so this is purely additive. */ function cae_agent_meta_author() { echo '<meta name="author" content="Joe Harvey">' . "\n"; } add_action( 'wp_head', 'cae_agent_meta_author', 4 ); /** * Markdown rendering of a single post/page/faq. * * @param WP_Post $post Post object. * @return string Markdown. */ function cae_agent_markdown_for_post( $post ) { $title = wp_strip_all_tags( get_the_title( $post ) ); $md = '# ' . $title . "\n\n"; $md .= get_permalink( $post ) . "\n\n"; $content = apply_filters( 'the_content', $post->post_content ); $text = wp_strip_all_tags( $content, false ); $text = trim( preg_replace( "/\n{3,}/", "\n\n", $text ) ); if ( '' === $text && ! empty( $post->post_excerpt ) ) { $text = wp_strip_all_tags( $post->post_excerpt ); } $md .= $text . "\n\n"; $md .= '---' . "\n"; $md .= 'Site: ' . wp_strip_all_tags( get_bloginfo( 'name' ) ) . ' - ' . home_url( '/' ) . "\n"; $md .= 'LLM index: ' . home_url( '/llms.txt' ) . ' | Full text: ' . home_url( '/llms-full.txt' ) . "\n"; return $md; } /** * Markdown rendering of the site index (/index.md). Prefers the curated, * physically-present llms.txt at ABSPATH if it exists; otherwise builds a * minimal index. * * @return string Markdown. */ function cae_agent_markdown_site_index() { $llms = ABSPATH . 'llms.txt'; if ( is_readable( $llms ) ) { $contents = file_get_contents( $llms ); if ( is_string( $contents ) && '' !== trim( $contents ) ) { return $contents; } } $md = '# ' . wp_strip_all_tags( get_bloginfo( 'name' ) ) . "\n\n"; $md .= wp_strip_all_tags( get_bloginfo( 'description' ) ) . "\n\n"; $md .= '- Home: ' . home_url( '/' ) . "\n"; $md .= '- LLM index: ' . home_url( '/llms.txt' ) . "\n"; $md .= '- Full text for LLMs: ' . home_url( '/llms-full.txt' ) . "\n"; $md .= '- Sitemap: ' . home_url( '/sitemap_index.xml' ) . "\n"; $md .= '- For AI agents: ' . home_url( '/agents/' ) . "\n"; return $md; } /** * Send a markdown response and end the request. * * @param string $md Markdown body. */ function cae_agent_serve_markdown( $md ) { if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } if ( ! headers_sent() ) { status_header( 200 ); header( 'Content-Type: text/markdown; charset=utf-8' ); header( 'Vary: Accept' ); header( 'Cache-Control: no-cache, max-age=0' ); header( 'X-Robots-Tag: noindex, follow' ); } echo $md; // Plain text/markdown output; tags already stripped. exit; } /** * .md URL routes: /index.md -> site index markdown; /{slug}.md (pages, * posts, faqs) -> that entry as markdown. Only fires on a literal .md * request path — no Accept-header sniffing here, so it cannot collide * with the parent engine's own content-negotiation handler. Unmatched * paths fall through to the normal WordPress 404. */ function cae_agent_md_suffix() { if ( is_admin() ) { return; } $uri = isset( $_SERVER['REQUEST_URI'] ) ? (string) $_SERVER['REQUEST_URI'] : ''; $uri = strtok( $uri, '?' ); if ( ! preg_match( '#^/(.*)\.md$#', $uri, $m ) ) { return; } $path = trim( $m[1], '/' ); if ( '' === $path || 'index' === strtolower( $path ) ) { cae_agent_serve_markdown( cae_agent_markdown_site_index() ); } $post = get_page_by_path( $path, OBJECT, array( 'page', 'post', 'faq' ) ); if ( $post instanceof WP_Post && 'publish' === $post->post_status ) { cae_agent_serve_markdown( cae_agent_markdown_for_post( $post ) ); } // No match: let WordPress 404 normally. } add_action( 'template_redirect', 'cae_agent_md_suffix', 1 ); https://citaeotion.ai/post-sitemap.xml 2026-07-20T07:01:01+00:00 https://citaeotion.ai/page-sitemap.xml 2026-07-20T06:57:58+00:00 https://citaeotion.ai/faq-sitemap.xml 2026-07-20T05:53:31+00:00 https://citaeotion.ai/faq_category-sitemap.xml 2026-07-20T05:53:31+00:00