$value ) { header( $name . ': ' . $value ); } echo $response['body']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain text; every value is stripped of tags and Markdown in line(). exit; } /** * The response for this request, or null when the plugin does not answer it. * * @return array|null Status, headers and body. */ public static function response() { if ( ! self::enabled() || ! self::is_request() || self::file_exists() || ! is_404() ) { return null; } /** * Whether the plugin answers the llms.txt address. Return false from a plugin that * serves its own llms.txt from template_include or later. * * @since 1.71.0 * * @param bool $serve Whether to serve the file. True when nothing else answered. */ if ( ! apply_filters( 'ai_chat_bedrock_llms_txt_serve', true ) ) { return null; } return array( 'status' => 200, 'headers' => array( 'Content-Type' => 'text/plain; charset=utf-8', 'X-Content-Type-Options' => 'nosniff', ), 'body' => self::text(), ); } /** * The file, from the cache when it is current. * * @return string */ public static function text() { $cached = get_transient( self::CACHE_KEY ); if ( is_string( $cached ) && '' !== $cached ) { return $cached; } $text = self::build(); set_transient( self::CACHE_KEY, $text, self::CACHE_TTL ); return $text; } /** * Forget the file, so the next request writes it again. Hooked to post and settings changes. */ public static function flush() { delete_transient( self::CACHE_KEY ); } /** * Write the file. * * @return string */ public static function build() { $name = self::line( get_bloginfo( 'name' ) ); $description = self::line( get_bloginfo( 'description' ) ); $languages = AI_Chat_Bedrock_Ontology::languages(); $out = '# ' . ( '' !== $name ? $name : self::line( home_url( '/' ) ) ) . "\n"; if ( '' !== $description ) { $out .= "\n> " . $description . "\n"; } if ( count( $languages ) > 1 ) { $out .= "\n" . sprintf( /* translators: %s: language names, separated by commas. */ __( 'Content is published in: %s.', 'ai-chat-for-amazon-bedrock' ), implode( ', ', array_map( array( __CLASS__, 'line' ), wp_list_pluck( $languages, 'name' ) ) ) ) . "\n"; } $sections = array(); foreach ( AI_Chat_Bedrock_Ontology::public_post_types() as $post_type ) { $posts = get_posts( array( 'post_type' => $post_type, 'post_status' => 'publish', 'has_password' => false, 'posts_per_page' => self::MAX_PER_TYPE, 'orderby' => 'modified', 'order' => 'DESC', 'no_found_rows' => true, 'lang' => '', ) ); foreach ( $posts as $post ) { $type = AI_Chat_Bedrock_Ontology::type_of( $post ); if ( '' === $type || ! AI_Chat_Bedrock_Ontology::allows( $post, 'agent' ) ) { continue; } if ( isset( $sections[ $type ] ) && count( $sections[ $type ] ) >= self::MAX_PER_TYPE ) { continue; } $title = self::line( AI_Chat_Bedrock_Content::title( $post ) ); $url = str_replace( array( '(', ')', ' ' ), array( '%28', '%29', '%20' ), esc_url_raw( (string) get_permalink( $post ) ) ); $item = '- [' . ( '' !== $title ? $title : $url ) . '](' . $url . ')'; if ( count( $languages ) > 1 ) { $language = AI_Chat_Bedrock_Content::language( $post ); if ( '' !== $language ) { $item .= ': ' . self::line( AI_Chat_Bedrock_Content::language_name( $language ) ); } } $sections[ $type ][] = $item; } } $types = AI_Chat_Bedrock_Ontology::types(); foreach ( $sections as $type => $items ) { $label = isset( $types[ $type ]['label'] ) ? $types[ $type ]['label'] : $type; $out .= "\n## " . self::line( $label ) . "\n\n" . implode( "\n", $items ) . "\n"; } /** * Filters the llms.txt text before it is cached and served. * * @since 1.71.0 * * @param string $out The Markdown text. */ return (string) apply_filters( 'ai_chat_bedrock_llms_txt', $out ); } /** * One value on one line, without tags or the brackets that would end a Markdown link text. * * @param mixed $value Value. * @return string */ public static function line( $value ) { $text = html_entity_decode( wp_strip_all_tags( (string) $value ), ENT_QUOTES, 'UTF-8' ); $text = str_replace( array( '[', ']' ), array( '(', ')' ), $text ); return trim( (string) preg_replace( '/\s+/u', ' ', $text ) ); } }