add_action( 'wp_head', [ $this, 'output_meta_tags' ], 1 ); // Remove default WP title if we provide one add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 99 ); // XML Sitemap add_action( 'init', [ $this, 'register_sitemap_route' ] ); add_action( 'save_post', [ $this, 'invalidate_sitemap' ] ); add_action( 'delete_post', [ $this, 'invalidate_sitemap' ] ); } // ─── Meta Box ─────────────────────────────────────────── public function add_meta_box(): void { $post_types = get_post_types( [ 'public' => true ] ); foreach ( $post_types as $pt ) { add_meta_box( 'airewp_seo', __( 'SEO Settings', 'ai-ready-wp' ), [ $this, 'render_meta_box' ], $pt, 'normal', 'high' ); } } public function render_meta_box( \WP_Post $post ): void { wp_nonce_field( 'airewp_seo_save', 'airewp_seo_nonce' ); $title = get_post_meta( $post->ID, self::META_KEY_TITLE, true ); $desc = get_post_meta( $post->ID, self::META_KEY_DESC, true ); $keyword = get_post_meta( $post->ID, self::META_KEY_KEYWORD, true ); $noindex = get_post_meta( $post->ID, self::META_KEY_NOINDEX, true ); $nofollow = get_post_meta( $post->ID, self::META_KEY_NOFOLLOW, true ); $canonical = get_post_meta( $post->ID, self::META_KEY_CANONICAL, true ); ?>

post_title ); $color = $len > 60 ? '#d63638' : ( $len > 50 ? '#dba617' : '#00a32a' ); printf( '%d/60 %s', esc_attr( $color ), absint( $len ), esc_html__( 'characters. Recommended: 50-60.', 'ai-ready-wp' ) ); ?>

160 ? '#d63638' : ( $dlen > 120 ? '#dba617' : '#00a32a' ); printf( '%d/160 %s', esc_attr( $dcolor ), absint( $dlen ), esc_html__( 'characters. Recommended: 120-160.', 'ai-ready-wp' ) ); ?>

post_content ) : ?>
✓' : '✗', [ 'span' => [ 'style' => [] ] ] ); ?>
'airewp_seo_title', self::META_KEY_DESC => 'airewp_seo_desc', self::META_KEY_KEYWORD => 'airewp_seo_keyword', self::META_KEY_CANONICAL => 'airewp_seo_canonical', ]; foreach ( $fields as $meta_key => $input_key ) { $value = isset( $_POST[ $input_key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $input_key ] ) ) : ''; if ( $meta_key === self::META_KEY_CANONICAL && $value ) { $value = esc_url_raw( $value ); } update_post_meta( $post_id, $meta_key, $value ); } // Checkboxes update_post_meta( $post_id, self::META_KEY_NOINDEX, ! empty( $_POST['airewp_seo_noindex'] ) ? '1' : '' ); update_post_meta( $post_id, self::META_KEY_NOFOLLOW, ! empty( $_POST['airewp_seo_nofollow'] ) ? '1' : '' ); } // ─── Meta Tags Output ─────────────────────────────────── public function output_meta_tags(): void { if ( is_admin() ) { return; } $post = get_queried_object(); if ( ! $post instanceof \WP_Post ) { $this->output_home_meta(); return; } $title = get_post_meta( $post->ID, self::META_KEY_TITLE, true ) ?: $post->post_title; $desc = get_post_meta( $post->ID, self::META_KEY_DESC, true ) ?: wp_trim_words( wp_strip_all_tags( $post->post_content ), 25, '...' ); $canonical = get_post_meta( $post->ID, self::META_KEY_CANONICAL, true ) ?: get_permalink( $post->ID ); $noindex = get_post_meta( $post->ID, self::META_KEY_NOINDEX, true ); $nofollow = get_post_meta( $post->ID, self::META_KEY_NOFOLLOW, true ); // Get OG image $image = get_the_post_thumbnail_url( $post->ID, 'large' ); if ( ! $image ) { $settings = AI_Ready_WP::get_settings(); $image = $settings['company_logo'] ?? ''; } // Meta description if ( $desc ) { printf( '' . "\n", esc_attr( $desc ) ); } // Canonical printf( '' . "\n", esc_url( $canonical ) ); // Robots $robots = []; if ( $noindex ) { $robots[] = 'noindex'; } if ( $nofollow ) { $robots[] = 'nofollow'; } if ( $robots ) { printf( '' . "\n", esc_attr( implode( ', ', $robots ) ) ); } // OpenGraph echo '' . "\n"; printf( '' . "\n", esc_attr( $title ) ); if ( $desc ) { printf( '' . "\n", esc_attr( $desc ) ); } printf( '' . "\n", esc_url( $canonical ) ); if ( $image ) { printf( '' . "\n", esc_url( $image ) ); } printf( '' . "\n", esc_attr( get_bloginfo( 'name' ) ) ); // Twitter Card echo '' . "\n"; printf( '' . "\n", esc_attr( $title ) ); if ( $desc ) { printf( '' . "\n", esc_attr( $desc ) ); } if ( $image ) { printf( '' . "\n", esc_url( $image ) ); } } private function output_home_meta(): void { if ( ! is_front_page() && ! is_home() ) { return; } $settings = AI_Ready_WP::get_settings(); $title = $settings['company_name'] ?? get_bloginfo( 'name' ); $desc = $settings['company_desc'] ?? get_bloginfo( 'description' ); $url = home_url( '/' ); $image = $settings['company_logo'] ?? ''; if ( $desc ) { printf( '' . "\n", esc_attr( $desc ) ); } printf( '' . "\n", esc_url( $url ) ); echo '' . "\n"; printf( '' . "\n", esc_attr( $title ) ); if ( $desc ) { printf( '' . "\n", esc_attr( $desc ) ); } printf( '' . "\n", esc_url( $url ) ); if ( $image ) { printf( '' . "\n", esc_url( $image ) ); } } public function filter_title( string $title ): string { if ( is_admin() || ! is_singular() ) { return $title; } $post = get_queried_object(); if ( ! $post instanceof \WP_Post ) { return $title; } $custom = get_post_meta( $post->ID, self::META_KEY_TITLE, true ); if ( $custom ) { return $custom . ' — ' . get_bloginfo( 'name' ); } return $title; } // ─── XML Sitemap ────────────────────────────────── public function register_sitemap_route(): void { add_rewrite_rule( '^sitemap\.xml$', 'index.php?airewp_sitemap=1', 'top' ); add_filter( 'query_vars', function ( array $vars ): array { $vars[] = 'airewp_sitemap'; return $vars; } ); add_action( 'template_redirect', [ $this, 'serve_sitemap' ] ); } public function serve_sitemap(): void { if ( ! get_query_var( 'airewp_sitemap' ) ) { return; } header( 'Content-Type: application/xml; charset=utf-8' ); header( 'X-Robots-Tag: noindex' ); echo '' . "\n"; echo '' . "\n"; // Homepage printf( " \n %s\n daily\n 1.0\n \n", esc_url( home_url( '/' ) ) ); // Pages $pages = get_posts( [ 'post_type' => 'page', 'post_status' => 'publish', 'posts_per_page' => 500, 'meta_query' => [ 'relation' => 'OR', [ 'key' => self::META_KEY_NOINDEX, 'compare' => 'NOT EXISTS', ], [ 'key' => self::META_KEY_NOINDEX, 'value' => '1', 'compare' => '!=', ], ], ] ); foreach ( $pages as $page ) { printf( " \n %s\n %s\n weekly\n 0.8\n \n", esc_url( get_permalink( $page->ID ) ), esc_html( get_the_modified_date( 'c', $page ) ) ); } // Posts $posts = get_posts( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 1000, 'meta_query' => [ 'relation' => 'OR', [ 'key' => self::META_KEY_NOINDEX, 'compare' => 'NOT EXISTS', ], [ 'key' => self::META_KEY_NOINDEX, 'value' => '1', 'compare' => '!=', ], ], ] ); foreach ( $posts as $p ) { printf( " \n %s\n %s\n monthly\n 0.6\n \n", esc_url( get_permalink( $p->ID ) ), esc_html( get_the_modified_date( 'c', $p ) ) ); } echo '' . "\n"; exit; } public function invalidate_sitemap(): void { delete_option( self::SITEMAP_OPTION ); } // ─── Focus Keyword Analysis ─────────────────────── /** * Analyze focus keyword placement. * * @return array */ public static function analyze_keyword( string $keyword, \WP_Post $post ): array { $kw_lower = mb_strtolower( $keyword ); $title = mb_strtolower( $post->post_title ); $content = mb_strtolower( wp_strip_all_tags( $post->post_content ) ); $slug = $post->post_name; $meta_desc = mb_strtolower( get_post_meta( $post->ID, self::META_KEY_DESC, true ) ?: '' ); $word_count = str_word_count( $content ); $kw_count = mb_substr_count( $content, $kw_lower ); $density = $word_count > 0 ? ( $kw_count / $word_count ) * 100 : 0; return [ [ 'label' => __( 'Keyword in title', 'ai-ready-wp' ), 'pass' => str_contains( $title, $kw_lower ), ], [ 'label' => __( 'Keyword in URL slug', 'ai-ready-wp' ), 'pass' => str_contains( $slug, sanitize_title( $keyword ) ), ], [ 'label' => __( 'Keyword in meta description', 'ai-ready-wp' ), 'pass' => $meta_desc && str_contains( $meta_desc, $kw_lower ), ], [ 'label' => sprintf( /* translators: %s: density percentage */ __( 'Keyword density: %s%% (aim for 1-3%%)', 'ai-ready-wp' ), number_format( $density, 1 ) ), 'pass' => $density >= 0.5 && $density <= 4.0, ], [ 'label' => sprintf( /* translators: %d: word count */ __( 'Content length: %d words (min 300)', 'ai-ready-wp' ), $word_count ), 'pass' => $word_count >= 300, ], [ 'label' => __( 'Keyword in first paragraph', 'ai-ready-wp' ), 'pass' => self::keyword_in_first_paragraph( $kw_lower, $content ), ], ]; } private static function keyword_in_first_paragraph( string $kw, string $content ): bool { $first = mb_substr( $content, 0, 500 ); return str_contains( $first, $kw ); } }