build_full_content( $settings ), array() ); exit; } AEOMATIC_Activity::bump( 'llms_serves' ); echo wp_kses( $this->build_content( $settings ), array() ); exit; } /** * Build the llms.txt body. */ private function build_content( array $settings ): string { $site_name = get_bloginfo( 'name' ); $site_url = home_url( '/' ); $tagline = get_bloginfo( 'description' ); $out = '# ' . $site_name . "\n\n"; $out .= '> ' . ( ! empty( $settings['description'] ) ? $settings['description'] : $tagline ) . "\n\n"; $out .= 'Site URL: ' . $site_url . "\n"; $out .= 'Full content: ' . home_url( '/llms-full.txt' ) . "\n\n"; // Key pages (those marked in primary nav menu). $key_pages = $this->get_key_pages(); if ( $key_pages ) { $out .= "## Key pages\n\n"; foreach ( $key_pages as $page ) { $out .= sprintf( "- [%s](%s)", $page['title'], $page['url'] ); if ( ! empty( $page['desc'] ) ) { $out .= ': ' . $page['desc']; } $out .= "\n"; } $out .= "\n"; } // Recent / featured content from every enabled post type. if ( ! empty( $settings['auto_include'] ) ) { $grouped = $this->get_recent_content_by_type(); foreach ( $grouped as $section ) { if ( empty( $section['posts'] ) ) { continue; } $out .= '## ' . $section['heading'] . "\n\n"; foreach ( $section['posts'] as $p ) { $out .= sprintf( "- [%s](%s)", $p['title'], $p['url'] ); if ( ! empty( $p['desc'] ) ) { $out .= ': ' . $p['desc']; } $out .= "\n"; } $out .= "\n"; } } // Extra URLs from settings. if ( ! empty( $settings['extra_urls'] ) ) { $urls = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', $settings['extra_urls'] ) ) ); if ( $urls ) { $out .= "## Additional resources\n\n"; foreach ( $urls as $url ) { $out .= '- ' . esc_url( $url ) . "\n"; } $out .= "\n"; } } $out .= "## Ignore\n\n"; $out .= "- /wp-admin/\n"; $out .= "- /wp-login.php\n"; $out .= "- /wp-json/\n"; $out .= "- /cart/\n"; $out .= "- /checkout/\n"; $out .= "- /my-account/\n"; $out .= "- /feed/\n"; $out .= "- /xmlrpc.php\n\n"; $out .= '---' . "\n"; $out .= 'Generated by AEOmatic — AI based Answer Engine Optimization v' . AEOMATIC_VERSION . ' on ' . gmdate( 'Y-m-d' ) . "\n"; return $out; } /** * Build the llms-full.txt body — the full extracted text of every * published page and post (across enabled post types), one Markdown * `##` block per item. This is the AI-readable full-content companion * to the llms.txt index. */ private function build_full_content( array $settings ): string { $site_name = get_bloginfo( 'name' ); $site_url = home_url( '/' ); $tagline = get_bloginfo( 'description' ); $out = '# ' . $site_name . ' — ' . __( 'Full content', 'aeomatic' ) . "\n\n"; $out .= '> ' . ( ! empty( $settings['description'] ) ? $settings['description'] : $tagline ) . "\n\n"; $out .= 'Site URL: ' . $site_url . "\n"; $out .= 'Index: ' . home_url( '/llms.txt' ) . "\n\n"; $enabled = AEOMATIC_Post_Types::get_enabled_slugs(); $objects = AEOMATIC_Post_Types::get_public_post_types(); $max_items = (int) apply_filters( 'aeomatic_llms_max_items', -1 ); foreach ( $enabled as $slug ) { if ( ! isset( $objects[ $slug ] ) ) { continue; } $query = new WP_Query( array( 'post_type' => $slug, 'post_status' => 'publish', 'posts_per_page' => $max_items, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ) ); foreach ( $query->posts as $post ) { $out .= '## ' . wp_strip_all_tags( get_the_title( $post ) ) . "\n\n"; $out .= 'URL: ' . get_permalink( $post ) . "\n\n"; $extracted = AEOMATIC_Content_Extractor::extract( $post ); $text = trim( (string) ( $extracted['text'] ?? '' ) ); if ( '' === $text ) { $text = trim( wp_strip_all_tags( get_the_excerpt( $post ) ) ); } $out .= ( '' !== $text ? $text : __( '(No extractable text content.)', 'aeomatic' ) ) . "\n\n"; } } $out .= '---' . "\n"; $out .= 'Generated by AEOmatic — AI based Answer Engine Optimization v' . AEOMATIC_VERSION . ' on ' . gmdate( 'Y-m-d' ) . "\n"; return $out; } /** * Get pages from the primary nav menu (best heuristic for "important pages"). */ private function get_key_pages(): array { $pages = array(); $locations = get_nav_menu_locations(); $menu_id = $locations['primary'] ?? ( reset( $locations ) ?: 0 ); if ( $menu_id ) { $items = wp_get_nav_menu_items( $menu_id ); if ( $items ) { foreach ( $items as $item ) { if ( '0' !== (string) $item->menu_item_parent ) { continue; // top-level only } $pages[] = array( 'title' => $item->title, 'url' => $item->url, 'desc' => $item->description, ); if ( count( $pages ) >= 10 ) { break; } } } } // Fallback: top-level published pages by menu_order. if ( empty( $pages ) ) { $wp_pages = get_pages( array( 'parent' => 0, 'sort_column' => 'menu_order,post_title', 'number' => 10, ) ); foreach ( (array) $wp_pages as $p ) { $pages[] = array( 'title' => get_the_title( $p ), 'url' => get_permalink( $p ), 'desc' => wp_strip_all_tags( get_the_excerpt( $p ) ), ); } } return $pages; } /** * Get published items grouped by post type. Skips post types the user * has disabled in settings. Pages are included as their own "Pages" * section (in addition to the curated nav-menu key-pages list above). * * Lists every published item — there is no fixed cap. Sites with very * large archives can bound the list per type via the * `aeomatic_llms_max_items` filter (default -1 = unlimited). * * @return array */ private function get_recent_content_by_type(): array { $enabled = AEOMATIC_Post_Types::get_enabled_slugs(); $objects = AEOMATIC_Post_Types::get_public_post_types(); $out = array(); /** Per-type item limit. -1 = all published items (no cap). */ $max_items = (int) apply_filters( 'aeomatic_llms_max_items', -1 ); foreach ( $enabled as $slug ) { $obj = $objects[ $slug ] ?? null; if ( ! $obj ) { continue; } $query = new WP_Query( array( 'post_type' => $slug, 'post_status' => 'publish', 'posts_per_page' => $max_items, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ) ); if ( empty( $query->posts ) ) { continue; } $posts = array(); foreach ( $query->posts as $post ) { $posts[] = array( 'title' => get_the_title( $post ), 'url' => get_permalink( $post ), 'desc' => wp_strip_all_tags( get_the_excerpt( $post ) ), ); } $out[] = array( 'heading' => $obj->labels->name ?? ucfirst( $slug ), 'posts' => $posts, ); } return $out; } /** * Legacy helper kept for backward compatibility. */ private function get_recent_posts(): array { $query = new WP_Query( array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 25, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ) ); $out = array(); foreach ( $query->posts as $post ) { $out[] = array( 'title' => get_the_title( $post ), 'url' => get_permalink( $post ), 'desc' => wp_strip_all_tags( get_the_excerpt( $post ) ), ); } return $out; } }