build_content(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain text output. exit; } /** * Build the llms.txt content. * * @return string The llms.txt content. */ public function build_content(): string { $store_name = get_bloginfo( 'name' ); $store_url = home_url(); $store_desc = get_bloginfo( 'description' ); $lines = array(); // Header. $lines[] = '# ' . $store_name; $lines[] = ''; $lines[] = '> ' . ( ! empty( $store_desc ) ? $store_desc : $store_name . ' — an online store.' ); $lines[] = ''; // Store info. $lines[] = '## Store Information'; $lines[] = ''; $lines[] = '- Website: ' . $store_url; if ( function_exists( 'get_woocommerce_currency' ) ) { $lines[] = '- Currency: ' . get_woocommerce_currency(); } if ( post_type_exists( 'product' ) ) { $product_count = wp_count_posts( 'product' ); if ( $product_count && isset( $product_count->publish ) ) { $lines[] = '- Products: ' . (int) $product_count->publish; } } $lines[] = ''; // Feeds. $lines[] = '## Product Feeds'; $lines[] = ''; $feed_types = array( 'google' => array( 'name' => 'Google Shopping Feed', 'file' => 'google-feed.xml', ), 'openai' => array( 'name' => 'OpenAI Product Feed', 'file' => 'openai-feed.json', ), 'bing' => array( 'name' => 'Bing Shopping Feed', 'file' => 'bing-feed.xml', ), 'perplexity' => array( 'name' => 'Perplexity Product Feed', 'file' => 'perplexity-feed.json', ), ); $upload_dir = wp_upload_dir(); $feed_dir = $upload_dir['basedir'] . '/aicommerce/feeds/'; foreach ( $feed_types as $type => $info ) { $file_path = $feed_dir . $info['file']; if ( file_exists( $file_path ) ) { $feed_url = $upload_dir['baseurl'] . '/aicommerce/feeds/' . $info['file']; $lines[] = '- [' . $info['name'] . '](' . $feed_url . ')'; } } $lines[] = ''; // API discovery. $lines[] = '## API Discovery'; $lines[] = ''; $lines[] = '- [AI Commerce Manifest](' . home_url( '/.well-known/ai-commerce.json' ) . ')'; $lines[] = '- [MCP Server](' . rest_url( 'aicommerce/v1/mcp' ) . ')'; $lines[] = '- [MCP Discovery](' . home_url( '/.well-known/mcp.json' ) . ')'; $sitemap_url = home_url( '/wp-sitemap.xml' ); $lines[] = '- [Sitemap](' . $sitemap_url . ')'; $lines[] = ''; // Top categories — helps LLMs discover the catalog structure. $category_lines = $this->build_category_lines(); if ( ! empty( $category_lines ) ) { $lines[] = '## Top Categories'; $lines[] = ''; foreach ( $category_lines as $line ) { $lines[] = $line; } $lines[] = ''; } // Crawling policy — reflects the store's actual configuration. $lines[] = '## Crawling Policy'; $lines[] = ''; $lines[] = 'This store welcomes AI agents and LLM crawlers. Product data is'; $lines[] = 'available via structured feeds and schema markup on product pages.'; $lines[] = ''; $blocked = get_option( 'aicommerce_blocked_bots', array() ); $blocked = is_array( $blocked ) ? array_values( array_filter( array_map( 'strval', $blocked ) ) ) : array(); if ( ! empty( $blocked ) ) { $lines[] = 'Disallowed AI crawler groups: ' . implode( ', ', $blocked ); } else { $lines[] = 'All major AI crawlers are permitted, subject to robots.txt.'; } $lines[] = ''; $lines[] = 'See ' . home_url( '/robots.txt' ) . ' for per-crawler rules.'; $lines[] = ''; $lines[] = 'For automated purchasing, see the API discovery links above.'; $lines[] = ''; // Contact — never exposes the WordPress admin email automatically. $contact_email = sanitize_email( (string) get_option( 'aicommerce_llms_contact_email', '' ) ); /** * Filters the public contact email published in /llms.txt. * * Empty by default so the site admin email is never leaked. Set the * aicommerce_llms_contact_email option or this filter to publish one. * * @param string $contact_email The contact email (may be empty). */ $contact_email = (string) apply_filters( 'aicommerce_llms_contact_email', $contact_email ); if ( ! empty( $contact_email ) && is_email( $contact_email ) ) { $lines[] = '## Contact'; $lines[] = ''; $lines[] = '- Email: ' . $contact_email; $lines[] = ''; } $content = implode( "\n", $lines ); /** * Filters the complete /llms.txt content before output. * * @param string $content The generated llms.txt content. */ return (string) apply_filters( 'aicommerce_llms_txt_content', $content ); } /** * Build markdown links for the top product categories. * * @param int $limit Maximum number of categories to include. * * @return array Markdown list lines. */ private function build_category_lines( int $limit = 20 ): array { if ( ! taxonomy_exists( 'product_cat' ) ) { return array(); } $terms = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => true, 'number' => $limit, 'orderby' => 'count', 'order' => 'DESC', ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { return array(); } $lines = array(); foreach ( $terms as $term ) { $link = get_term_link( $term ); if ( is_wp_error( $link ) ) { continue; } $lines[] = '- [' . $term->name . '](' . $link . ') — ' . (int) $term->count . ' products'; } return $lines; } }