set_404(); status_header( 404 ); nocache_headers(); include get_404_template(); exit; } private static function send( $body, $type ) { status_header( 200 ); header( 'Content-Type: ' . $type ); header( 'X-Robots-Tag: noindex, follow', true ); header( 'Cache-Control: public, max-age=900' ); echo $body; // phpcs:ignore WordPress.Security.EscapeOutput exit; } private static function cached( $key, $builder ) { $k = 'sxseo_' . substr( md5( $key . '|' . get_option( 'sxseo_cache_ver', '0' ) ), 0, 20 ); $v = get_transient( $k ); if ( false === $v ) { $v = call_user_func( $builder ); set_transient( $k, $v, 6 * HOUR_IN_SECONDS ); } return $v; } // ------------------------------------------------------------------- queries private static function base_args( $type ) { return array( 'post_type' => $type, 'post_status' => 'publish', 'has_password' => false, 'no_found_rows' => true, 'update_post_term_cache' => false, 'meta_query' => array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_sxseo_noindex', 'compare' => 'NOT EXISTS' ), array( 'key' => '_sxseo_noindex', 'value' => '1', 'compare' => '!=' ), ), array( 'relation' => 'OR', array( 'key' => '_sxseo_exclude_sitemap', 'compare' => 'NOT EXISTS' ), array( 'key' => '_sxseo_exclude_sitemap', 'value' => '1', 'compare' => '!=' ), ), ), ); } private static function count_posts( $type ) { $q = new WP_Query( array_merge( self::base_args( $type ), array( 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => false ) ) ); return (int) $q->found_posts; } private static function terms_for( $tax ) { $terms = get_terms( array( 'taxonomy' => $tax, 'hide_empty' => true, 'number' => 0 ) ); if ( is_wp_error( $terms ) ) { return array(); } return array_values( array_filter( $terms, function ( $t ) { return sxseo_term_indexable( $t ) && 'uncategorized' !== $t->slug; } ) ); } // -------------------------------------------------------------------- builders public static function build_index() { $x = '' . "\n" . '' . "\n"; $latest = gmdate( 'c' ); foreach ( sxseo_post_types() as $type ) { $n = self::count_posts( $type ); if ( 0 === $n && ! ( 'page' === $type ) ) { continue; } $pages = max( 1, (int) ceil( $n / self::PER_PAGE ) ); for ( $p = 1; $p <= $pages; $p++ ) { $x .= '' . sxseo_x( home_url( "/sitemap-p-{$type}-{$p}.xml" ) ) . '' . $latest . '' . "\n"; } } foreach ( sxseo_taxonomies() as $tax ) { if ( self::terms_for( $tax ) ) { $x .= '' . sxseo_x( home_url( "/sitemap-t-{$tax}-1.xml" ) ) . '' . $latest . '' . "\n"; } } return $x . ''; } public static function build_map( $kind, $name, $page ) { $head = '' . "\n" . '' . "\n"; $rows = ''; if ( 'p' === $kind && in_array( $name, sxseo_post_types(), true ) ) { if ( 1 === $page && ( 'page' === $name ) && 'page' !== get_option( 'show_on_front' ) ) { $rows .= '' . sxseo_x( home_url( '/' ) ) . 'daily1.0' . "\n"; } $q = new WP_Query( array_merge( self::base_args( $name ), array( 'posts_per_page' => self::PER_PAGE, 'paged' => $page, 'orderby' => 'modified', 'order' => 'DESC' ) ) ); $front = (int) get_option( 'page_on_front' ); foreach ( $q->posts as $post ) { $loc = get_permalink( $post ); $prio = $post->ID === $front ? '1.0' : ( 'page' === $post->post_type ? '0.8' : '0.6' ); $rows .= '' . sxseo_x( $loc ) . '' . sxseo_w3c( $post->post_modified_gmt ) . '' . ( $post->ID === $front ? 'daily' : 'weekly' ) . '' . $prio . ''; if ( sxseo_opt( 'sitemap_images' ) ) { foreach ( self::images_for( $post ) as $img ) { $rows .= '' . sxseo_x( $img['url'] ) . '' . ( $img['title'] ? '' . sxseo_x( $img['title'] ) . '' : '' ) . ''; } } $rows .= "\n"; } if ( ! $q->posts && 1 !== $page ) { return ''; } } elseif ( 't' === $kind && in_array( $name, sxseo_taxonomies(), true ) ) { foreach ( self::terms_for( $name ) as $t ) { $link = get_term_link( $t ); if ( ! is_wp_error( $link ) ) { $rows .= '' . sxseo_x( $link ) . 'weekly0.4' . "\n"; } } } else { return ''; } return $head . $rows . ''; } private static function images_for( WP_Post $post ) { $imgs = array(); $thumb = get_post_thumbnail_id( $post ); if ( $thumb ) { $u = wp_get_attachment_image_url( $thumb, 'full' ); if ( $u ) { $imgs[ $u ] = array( 'url' => $u, 'title' => sxseo_title_text( $thumb ) ); } } if ( preg_match_all( '/]+src=["\']([^"\']+)["\'][^>]*>/i', $post->post_content, $m ) ) { foreach ( array_slice( $m[1], 0, 8 ) as $src ) { if ( 0 === strpos( $src, '//' ) ) { $src = 'https:' . $src; } if ( 0 === strpos( $src, '/' ) ) { $src = home_url( $src ); } if ( 0 === strpos( $src, 'http' ) && ! isset( $imgs[ $src ] ) ) { $imgs[ $src ] = array( 'url' => $src, 'title' => '' ); } } } return array_slice( array_values( $imgs ), 0, 10 ); } /** llms.txt: a plain-language map of the site for AI assistants and answer engines. */ public static function build_llms() { $name = get_bloginfo( 'name' ); $out = "# {$name}\n\n"; $desc = (string) sxseo_opt( 'home_description' ) ?: get_bloginfo( 'description' ); if ( $desc ) { $out .= '> ' . $desc . "\n\n"; } $o = SXSEO_Settings::all(); $bits = array_filter( array( $o['org_phone'], $o['org_email'], trim( implode( ', ', array_filter( array( $o['org_street'], $o['org_city'], $o['org_postcode'] ) ) ) ) ) ); if ( $bits ) { $out .= 'Contact: ' . implode( ' | ', $bits ) . "\n\n"; } $pages = get_posts( array( 'post_type' => 'page', 'post_status' => 'publish', 'numberposts' => 50, 'orderby' => 'menu_order title', 'order' => 'ASC', 'has_password' => false ) ); if ( $pages ) { $out .= "## Pages\n\n"; foreach ( $pages as $p ) { if ( '1' === get_post_meta( $p->ID, '_sxseo_noindex', true ) ) { continue; } $d = SXSEO_Context::for_post( $p ); $out .= '- [' . sxseo_title_text( $p ) . '](' . get_permalink( $p ) . '): ' . $d['desc'] . "\n"; } $out .= "\n"; } foreach ( array_diff( sxseo_post_types(), array( 'page' ) ) as $type ) { $posts = get_posts( array( 'post_type' => $type, 'post_status' => 'publish', 'numberposts' => 40, 'has_password' => false ) ); if ( ! $posts ) { continue; } $obj = get_post_type_object( $type ); $out .= '## ' . ( $obj ? $obj->labels->name : $type ) . "\n\n"; foreach ( $posts as $p ) { if ( '1' === get_post_meta( $p->ID, '_sxseo_noindex', true ) ) { continue; } $d = SXSEO_Context::for_post( $p ); $out .= '- [' . sxseo_title_text( $p ) . '](' . get_permalink( $p ) . '): ' . $d['desc'] . "\n"; } $out .= "\n"; } $out .= "## Sitemap\n\n- " . home_url( '/sitemap.xml' ) . "\n"; return $out; } // --------------------------------------------------------------------- robots public static function robots_txt( $output, $public ) { if ( ! $public ) { return $output; } if ( sxseo_opt( 'robots_txt_extras' ) ) { $output = rtrim( $output ) . "\n"; $output .= "Disallow: /?s=\nDisallow: /search/\nDisallow: /*?replytocom=\nDisallow: /*?add-to-cart=\n"; } if ( 'block_training' === sxseo_opt( 'ai_policy' ) ) { foreach ( array( 'GPTBot', 'CCBot', 'Google-Extended', 'anthropic-ai', 'ClaudeBot', 'Bytespider', 'Applebot-Extended', 'meta-externalagent' ) as $bot ) { $output .= "\nUser-agent: {$bot}\nDisallow: /\n"; } } if ( sxseo_opt( 'sitemap' ) ) { $output .= "\nSitemap: " . home_url( '/sitemap.xml' ) . "\n"; } return $output; } // ------------------------------------------------------------------- IndexNow public static function on_transition( $new, $old, $post ) { if ( ! sxseo_opt( 'indexnow' ) || ! get_option( 'blog_public' ) || wp_is_post_revision( $post ) ) { return; } if ( ! in_array( $post->post_type, sxseo_post_types(), true ) ) { return; } if ( 'publish' !== $new && 'publish' !== $old ) { return; } if ( 'publish' === $new && '1' === get_post_meta( $post->ID, '_sxseo_noindex', true ) ) { return; } self::submit( array( get_permalink( $post ) ) ); } public static function submit( array $urls ) { $host = sxseo_home_host(); $key = (string) sxseo_opt( 'indexnow_key' ); if ( '' === $key || ! $host || in_array( $host, array( 'localhost', '127.0.0.1' ), true ) || substr( $host, -6 ) === '.local' ) { return; } $fresh = array(); foreach ( $urls as $u ) { $t = 'sxseo_in_' . md5( $u ); if ( false === get_transient( $t ) ) { set_transient( $t, 1, 10 * MINUTE_IN_SECONDS ); $fresh[] = $u; } } if ( ! $fresh ) { return; } wp_remote_post( 'https://api.indexnow.org/indexnow', array( 'blocking' => false, 'timeout' => 3, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( array( 'host' => $host, 'key' => $key, 'keyLocation' => home_url( '/' . $key . '.txt' ), 'urlList' => $fresh ) ), ) ); } }