<?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 →</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