0, 'intro' => '' ) ); } public static function enabled() { return self::opts()['enabled'] && ! file_exists( ABSPATH . 'llms.txt' ) && '' === self::other(); } /** Name of an SEO plugin that already makes llms.txt on this site, or ''. StorePinger then stays out of the way. */ public static function other() { static $name = null; if ( null !== $name ) { return $name; } $name = ''; try { if ( class_exists( 'WPSEO_Options' ) && method_exists( 'WPSEO_Options', 'get' ) && WPSEO_Options::get( 'enable_llms_txt' ) ) { $name = 'Yoast SEO'; } elseif ( class_exists( '\RankMath\Helper' ) && method_exists( '\RankMath\Helper', 'is_module_active' ) && \RankMath\Helper::is_module_active( 'llms-txt' ) ) { $name = 'Rank Math'; } elseif ( function_exists( 'aioseo' ) && is_object( aioseo() ) && isset( aioseo()->options ) && aioseo()->options->sitemap->llms->enable ) { $name = 'All in One SEO'; } } catch ( \Throwable $e ) { $name = ''; } if ( '' === $name ) { $active = array_merge( (array) get_option( 'active_plugins', array() ), array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) ); foreach ( $active as $p ) { if ( preg_match( '/llms[-_]?txt/i', (string) $p ) ) { $name = __( 'another plugin', 'storepinger' ); break; } } } return $name; } public static function flush() { delete_transient( 'storepinger_llms_txt' ); } public static function serve() { $path = wp_parse_url( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '', PHP_URL_PATH ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $home = rtrim( (string) wp_parse_url( home_url( '/' ), PHP_URL_PATH ), '/' ); if ( $home . '/llms.txt' !== $path || ! self::enabled() ) { return; } status_header( 200 ); header( 'Content-Type: text/plain; charset=utf-8' ); header( 'X-Robots-Tag: noindex' ); echo self::build(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } private static function line( $post ) { // Only the public excerpt: page content can be behind a membership or restriction plugin. $desc = has_excerpt( $post ) ? wp_trim_words( wp_strip_all_tags( $post->post_excerpt ), 30, '…' ) : ''; $desc = trim( preg_replace( '/\s+/', ' ', html_entity_decode( $desc, ENT_QUOTES, 'UTF-8' ) ) ); return '- [' . html_entity_decode( get_the_title( $post ), ENT_QUOTES, 'UTF-8' ) . '](' . get_permalink( $post ) . ')' . ( $desc ? ': ' . $desc : '' ) . "\n"; } public static function build() { $cached = get_transient( 'storepinger_llms_txt' ); if ( is_string( $cached ) ) { return $cached; } $o = self::opts(); $shown = array(); $out = '# ' . html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES, 'UTF-8' ) . "\n\n"; $intro = trim( $o['intro'] ) ? $o['intro'] : get_bloginfo( 'description' ); if ( $intro ) { $out .= '> ' . trim( preg_replace( '/\s+/', ' ', html_entity_decode( $intro, ENT_QUOTES, 'UTF-8' ) ) ) . "\n\n"; } $groups = array( 'page' => array( __( 'Pages', 'storepinger' ), 50 ), 'product' => array( __( 'Products', 'storepinger' ), 100 ), 'post' => array( __( 'Blog', 'storepinger' ), 30 ), ); foreach ( $groups as $type => $g ) { if ( ! post_type_exists( $type ) ) { continue; } $posts = get_posts( array( 'post_type' => $type, 'post_status' => 'publish', 'posts_per_page' => $g[1], 'orderby' => 'page' === $type ? 'menu_order' : 'date', 'order' => 'page' === $type ? 'ASC' : 'DESC', 'has_password' => false, 'tax_query' => 'product' === $type && taxonomy_exists( 'product_visibility' ) ? array( array( 'taxonomy' => 'product_visibility', 'field' => 'slug', 'terms' => array( 'exclude-from-catalog', 'exclude-from-search' ), 'operator' => 'NOT IN' ) ) : array(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- hidden products stay out. ) ); if ( 'page' === $type && function_exists( 'wc_get_page_id' ) ) { $skip = array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ), wc_get_page_id( 'myaccount' ) ); $posts = array_filter( $posts, function ( $p ) use ( $skip ) { return ! in_array( (int) $p->ID, $skip, true ); } ); } if ( 'product' === $type ) { $shown = wp_list_pluck( $posts, 'ID' ); } if ( ! $posts ) { continue; } $out .= '## ' . $g[0] . "\n\n"; foreach ( $posts as $p ) { $out .= self::line( $p ); } $out .= "\n"; } if ( function_exists( 'get_terms' ) && taxonomy_exists( 'product_cat' ) ) { // Only categories of the products listed above, so hidden products never reveal a category. $cats = empty( $shown ) ? array() : wp_get_object_terms( $shown, 'product_cat' ); $cats = is_wp_error( $cats ) ? array() : array_slice( array_values( array_unique( $cats, SORT_REGULAR ) ), 0, 30 ); if ( $cats && ! is_wp_error( $cats ) ) { $out .= '## ' . __( 'Product categories', 'storepinger' ) . "\n\n"; foreach ( $cats as $c ) { $out .= '- [' . html_entity_decode( $c->name, ENT_QUOTES, 'UTF-8' ) . '](' . get_term_link( $c ) . ")\n"; } $out .= "\n"; } } set_transient( 'storepinger_llms_txt', $out, DAY_IN_SECONDS ); return $out; } public static function save() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'storepinger' ) ); } check_admin_referer( 'storepinger_llms' ); update_option( 'storepinger_llms', array( 'enabled' => empty( $_POST['enabled'] ) ? 0 : 1, 'intro' => isset( $_POST['intro'] ) ? sanitize_textarea_field( wp_unslash( $_POST['intro'] ) ) : '', ), false ); self::flush(); wp_safe_redirect( admin_url( 'admin.php?page=storepinger&spl=saved#spl-llms' ) ); exit; } /** Small form shown under the AI visibility score. */ public static function form() { if ( file_exists( ABSPATH . 'llms.txt' ) ) { return '
' . esc_html__( 'Your site already has its own llms.txt file.', 'storepinger' ) . '
'; } if ( '' !== self::other() ) { /* translators: %s: SEO plugin name */ return '' . esc_html( sprintf( __( 'Your llms.txt is already made by %s, so StorePinger does not create a second one.', 'storepinger' ), self::other() ) ) . ' ' . esc_html__( 'View llms.txt', 'storepinger' ) . '
'; } $o = self::opts(); $h = ''; } }