Plugin Directory

Changeset 3791583


Ignore:
Timestamp:
11/08/2026 10:32:17 PM (less than one hour ago)
Author:
videowhisper
Message:

Release 1.9.0: multilingual discovery, indexed sitemap SEO policy and reviewed traffic recovery compatibility.

Location:
videowhisper-agentic-optimization
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • videowhisper-agentic-optimization/tags/1.9.0/includes/class-vwao-discovery-service.php

    r3786442 r3791583  
    9292                        }
    9393                }
    94                 return hash( 'sha256', wp_json_encode( array( $this->settings->get_all(), VWAO_Multilingual::locale(), VWAO_SEO_Service::get_providers(), get_option( 'blog_public' ), $items ) ) );
     94                return hash( 'sha256', wp_json_encode( array( $this->settings->get_all(), VWAO_Multilingual::locale(), VWAO_SEO_Service::get_providers(), get_option( 'blog_public' ), self::configured_languages(), $items ) ) );
     95        }
     96        /** Configured choices are distinct from each item's complete published variants. */
     97        private static function configured_languages() {
     98                $rows = apply_filters( 'vwml_available_languages_v1', array(), 'discovery' );
     99                return is_array( $rows ) ? array_values( array_filter( array_keys( $rows ), static fn( $code ) => is_string( $code ) && preg_match( '/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*$/D', $code ) ) ) : array();
     100        }
     101        private function language_lines() {
     102                $codes = self::configured_languages();
     103                if ( ! $codes ) { return array(); }
     104                $lines = array( 'Configured languages: ' . implode( ', ', $codes ), 'Only complete published translations appear in language-specific results.' );
     105                foreach ( $codes as $code ) {
     106                        $lines[] = '- ' . $code . ': ' . esc_url_raw( add_query_arg( 'language', $code, home_url( '/llms.txt' ) ) ); }
     107                return $lines;
    95108        }
    96109
    … …  
    256269                $lines[] = 'Base URL: ' . $site['base_url'];
    257270                $lines[] = 'Language: ' . VWAO_Multilingual::language();
     271                $lines = array_merge( $lines, $this->language_lines() );
    258272                $lines[] = 'Generated (GMT): ' . gmdate( 'c' );
    259273                $lines[] = '';
    … …  
    323337                $lines[] = 'Base URL: ' . $site['base_url'];
    324338                $lines[] = 'Language: ' . VWAO_Multilingual::language();
     339                $lines = array_merge( $lines, $this->language_lines() );
    325340                $lines[] = 'Generated (GMT): ' . gmdate( 'c' );
    326341                $lines[] = '';
    … …  
    435450                $xml  = '<?xml version="1.0" encoding="' . esc_attr( get_bloginfo( 'charset' ) ) . '"?>';
    436451                $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:agent="urn:videowhisper:agentic:1.0">';
     452                $xml .= '<!-- Configured languages: ' . esc_html( implode( ', ', self::configured_languages() ) ) . '; per-URL available_languages lists complete published variants. -->';
    437453
    438454                foreach ( $content as $group ) {
  • videowhisper-agentic-optimization/tags/1.9.0/includes/class-vwao-multilingual.php

    r3790761 r3791583  
    7474        /** Register rendering hooks. */
    7575        public static function hooks() {
     76                add_filter( 'vwml_sitemap_source_allowed_v1', static function ( $allowed, $post ) {
     77                        if ( false === $allowed ) { return false; }
     78                        $seo = VWAO_SEO_Service::get_post_state( $post );
     79                        return false === ( $seo['noindex'] ?? null ) && untrailingslashit( $seo['canonical'] ?? '' ) === untrailingslashit( get_permalink( $post ) );
     80                }, 10, 2 );
    7681                add_filter(
    7782                        'vwml_use_public_overlay',
  • videowhisper-agentic-optimization/tags/1.9.0/includes/class-vwao-recovery.php

    r3791079 r3791583  
    2020                add_action( 'shutdown', array( __CLASS__, 'capture' ) );
    2121                add_action( 'vwao_recovery_cleanup', array( __CLASS__, 'prune' ) );
     22                add_filter( 'vwml_legacy_source_id_v1', array( __CLASS__, 'translation_source' ), 10, 4 );
     23        }
     24        /** Resolve only an existing, reviewed local redirect; never guess a target. */
     25        public static function translation_source( $id, $path, $locale = '', $request_path = '' ) {
     26                unset( $locale );
     27                if ( $id || '1' !== get_option( 'vwao_recovery_schema' ) ) { return $id; }
     28                global $wpdb;
     29                foreach ( array_unique( array( self::path( $path ), self::path( $request_path ) ) ) as $candidate ) {
     30                        if ( ! $candidate || self::decision( $candidate ) ) { continue; }
     31                        $r = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE path_hash=%s AND state=%s', $wpdb->prefix . 'vwao_recovery_paths', hash( 'sha256', $candidate ), 'redirect' ), ARRAY_A );
     32                        $p = $r && hash_equals( $r['path'], $candidate ) ? self::target( (int) $r['target_id'] ) : null;
     33                        if ( $p ) { return (int) $p->ID; }
     34                }
     35                return $id;
    2236        }
    2337        public static function settings() {
    … …  
    223237        }
    224238        public static function version( $row ) {
    225                 return hash( 'sha256', wp_json_encode( array( get_current_blog_id(), $row['id'], $row['path'], $row['state'], $row['target_id'], $row['redirect_code'], $row['draft_id'], $row['revision'] ) ) );
     239                // Database rows and report rows use different scalar types. Bind the same
     240                // normalized editorial state in both; traffic counters are not permissions.
     241                return hash( 'sha256', wp_json_encode( array( get_current_blog_id(), (int) $row['id'], (string) $row['path'], (string) $row['state'], (int) $row['target_id'], (int) $row['redirect_code'], (int) $row['draft_id'], (int) $row['revision'] ) ) );
    226242        }
    227243        public static function target( $id ) {
    … …  
    395411                                $p = self::target( $target );
    396412                                $existing = self::existing_id( $r['path'] );
    397                                 $draft = ! empty( $r['draft_id'] ) ? (int) $r['draft_id'] : 0;
    398                                 if ( $draft ) { clean_post_cache( $draft ); }
    399                                 // A linked editable recovery draft is retained, never overwritten or published.
    400                                 $source_conflict = $existing && ( (int) $existing !== $draft || ! self::reusable_draft( $r ) );
     413                                // Existing unpublished drafts remain untouched. A later real public
     414                                // page wins because runtime recovery only runs for an actual 404.
     415                                $source_conflict = $existing && is_post_publicly_viewable( get_post( $existing ) );
    401416                                if ( ! $p || ! in_array( $code, array( 301, 302 ), true ) || $source_conflict || self::decision( $r['path'] ) || self::same_path( get_permalink( $p ), $r['path'] ) ) {
    402417                                        return self::error(); }
  • videowhisper-agentic-optimization/tags/1.9.0/includes/class-vwao-standalone-seo.php

    r3789597 r3791583  
    116116                add_filter( 'wp_robots', array( __CLASS__, 'robots' ), 20 );
    117117                add_filter( 'wp_sitemaps_posts_query_args', array( __CLASS__, 'sitemap_query' ), 20, 2 );
     118                add_filter( 'posts_where', array( __CLASS__, 'sitemap_where' ), 20, 2 );
    118119        }
    119120
    … …  
    346347                        return $args; }
    347348                $args['has_password'] = false;
    348                 $clauses              = array( 'relation' => 'AND' );
    349                 if ( ! empty( $args['meta_query'] ) ) {
    350                         $clauses[] = $args['meta_query']; }
     349                $args['vwao_sitemap_seo'] = true;
     350                return $args;
     351        }
     352
     353        /** Indexed exclusions avoid multiplying theme metadata rows across six joins. */
     354        public static function sitemap_where( $where, $query ) {
     355                if ( true !== $query->get( 'vwao_sitemap_seo' ) || ! self::active() ) { return $where; }
     356                global $wpdb;
    351357                foreach ( array(
    352                         '_vwao_seo_noindex'                => array( '1' ),
     358                        '_vwao_seo_noindex' => array( '1' ),
    353359                        '_yoast_wpseo_meta-robots-noindex' => array( '1' ),
    354                         '_aioseo_robots_noindex'           => array( '1', 'on', 'yes' ),
    355                         '_seopress_robots_index'           => array( 'yes' ),
     360                        '_aioseo_robots_noindex' => array( '1', 'on', 'yes' ),
     361                        '_seopress_robots_index' => array( 'yes' ),
    356362                ) as $key => $values ) {
    357                         $clauses[] = array(
    358                                 'relation' => 'OR',
    359                                 array(
    360                                         'key'     => $key,
    361                                         'compare' => 'NOT EXISTS',
    362                                 ),
    363                                 array(
    364                                         'key'     => $key,
    365                                         'value'   => $values,
    366                                         'compare' => 'NOT IN',
    367                                 ),
    368                         );
    369                 }
    370                 $clauses[] = array(
    371                         'relation' => 'OR',
    372                         array(
    373                                 'key'     => 'rank_math_robots',
    374                                 'compare' => 'NOT EXISTS',
    375                         ),
    376                         array(
    377                                 'key'     => 'rank_math_robots',
    378                                 'value'   => '"noindex"',
    379                                 'compare' => 'NOT LIKE',
    380                         ),
    381                 );
    382                 // Custom canonical destinations are not advertised as self-canonical sitemap URLs.
    383                 $clauses[]          = array(
    384                         'relation' => 'OR',
    385                         array(
    386                                 'key'     => '_vwao_seo_canonical',
    387                                 'compare' => 'NOT EXISTS',
    388                         ),
    389                         array(
    390                                 'key'     => '_vwao_seo_canonical',
    391                                 'value'   => '',
    392                                 'compare' => '=',
    393                         ),
    394                 );
    395                 $args['meta_query'] = $clauses;
    396                 return $args;
     363                        $holders = implode( ',', array_fill( 0, count( $values ), '%s' ) );
     364                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers and generated placeholder list; values are prepared.
     365                        $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value IN ($holders))", array_merge( array( $key ), $values ) );
     366                }
     367                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers; values are prepared.
     368                $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value LIKE %s)", 'rank_math_robots', '%' . $wpdb->esc_like( '"noindex"' ) . '%' );
     369                // Custom canonical destinations are excluded, including duplicate conflicting metadata.
     370                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers; values are prepared.
     371                $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value<>%s)", '_vwao_seo_canonical', '' );
     372                return $where;
    397373        }
    398374
  • videowhisper-agentic-optimization/tags/1.9.0/languages/videowhisper-agentic-optimization.pot

    r3791079 r3791583  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Agentic Optimization - SEO for AI Agents 1.8.0\n"
     5"Project-Id-Version: Agentic Optimization - SEO for AI Agents 1.9.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/videowhisper-agentic-optimization\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-11-08T13:59:03+00:00\n"
     12"POT-Creation-Date: 2026-11-08T21:05:52+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    695695
    696696#. translators: 1: result cap, 2: max pages, 3: llms list cap.
    697 #: includes/class-vwao-discovery-service.php:250
     697#: includes/class-vwao-discovery-service.php:263
    698698#, php-format
    699699msgid "Use the Search endpoint for discovery, read specific items with Content or Markdown by numeric ID or content slug, including hierarchical paths (e.g., /content/25, /content/item1/item2/item3, or /markdown/item1/item2/item3). All listed endpoints are read-only and expose public content only. Search returns up to %1$d results per request across %2$d pages. This llms.txt file lists up to %3$d items per enabled content type, and pages can be unlimited when that option is enabled."
    700700msgstr ""
    701701
    702 #: includes/class-vwao-discovery-service.php:261
    703 #: includes/class-vwao-discovery-service.php:328
     702#: includes/class-vwao-discovery-service.php:275
     703#: includes/class-vwao-discovery-service.php:343
    704704msgid "Agent Endpoints"
    705705msgstr ""
    706706
    707 #: includes/class-vwao-discovery-service.php:262
    708 #: includes/class-vwao-discovery-service.php:329
     707#: includes/class-vwao-discovery-service.php:276
     708#: includes/class-vwao-discovery-service.php:344
    709709msgid "Discovery Files"
    710710msgstr ""
    711711
    712712#. translators: 1: item cap, 2: word cap.
    713 #: includes/class-vwao-discovery-service.php:318
     713#: includes/class-vwao-discovery-service.php:332
    714714#, php-format
    715715msgid "This expanded context file includes up to %1$d items per enabled content type and up to %2$d words per item to keep context manageable on large sites."
    … …  
    766766
    767767#: includes/class-vwao-multilingual.php:30
    768 #: includes/class-vwao-multilingual.php:108
     768#: includes/class-vwao-multilingual.php:112
    769769msgid "Invalid content language."
    770770msgstr ""
    … …  
    10731073msgstr ""
    10741074
    1075 #: includes/class-vwao-recovery.php:277
     1075#: includes/class-vwao-recovery.php:287
    10761076msgid "Administrator access is required."
    10771077msgstr ""
    10781078
    1079 #: includes/class-vwao-recovery.php:459
     1079#: includes/class-vwao-recovery.php:469
    10801080msgid "The recovery item, destination or permissions changed. Review the current item again."
    10811081msgstr ""
    10821082
    1083 #: includes/class-vwao-recovery.php:463
     1083#: includes/class-vwao-recovery.php:473
    10841084msgid "Traffic Recovery storage is unavailable. Review database access, then save recovery settings to retry setup."
    10851085msgstr ""
  • videowhisper-agentic-optimization/tags/1.9.0/readme.txt

    r3791079 r3791583  
    108108* Analytics disabled by default
    109109
     110= Multilingual discovery =
     111
     112When Agentic Multilingual Translator is active, discovery indexes list configured language choices separately from each page's complete published variants. Agent-facing translated content retains the original URL and available-language retrieval links. Reviewed local Traffic Recovery redirects can resolve old source slugs for legacy language URLs. Language sitemap entries respect the current original page's SEO indexing and canonical policy.
     113
    110114== Installation ==
    111115
    … …  
    222226== Changelog ==
    223227
     228= 1.9.0 =
     229* Keep core sitemaps responsive on metadata-heavy builder pages with indexed SEO exclusions. Retain unpublished drafts when reviewing a missing-URL redirect.
     230* Discovery indexes list configured languages separately from complete published variants.
     231* Reviewed traffic recovery targets can resolve legacy translated permalinks; sitemap entries respect current source SEO policy.
     232
     233
    224234= 1.8.0 =
    225235* Add opt-in, bounded Traffic Recovery reports, configurable retention and private Site Manager reporting.
  • videowhisper-agentic-optimization/tags/1.9.0/videowhisper-agentic-optimization.php

    r3791079 r3791583  
    33 * Plugin Name: Agentic Optimization - SEO for AI Agents
    44 * Description: Optional local search SEO editing plus agent-friendly discovery, markdown and read-only public REST/MCP access.
    5  * Version:     1.8.0
     5 * Version:     1.9.0
    66 * Requires at least: 6.9
    77 * Requires PHP: 8.2
    … …  
    1919defined( 'ABSPATH' ) || exit;
    2020
    21 define( 'VWAO_VERSION', '1.8.0' );
     21define( 'VWAO_VERSION', '1.9.0' );
    2222define( 'VWAO_REWRITE_VERSION', '3' );
    2323define( 'VWAO_PLUGIN_FILE', __FILE__ );
  • videowhisper-agentic-optimization/trunk/includes/class-vwao-discovery-service.php

    r3786442 r3791583  
    9292                        }
    9393                }
    94                 return hash( 'sha256', wp_json_encode( array( $this->settings->get_all(), VWAO_Multilingual::locale(), VWAO_SEO_Service::get_providers(), get_option( 'blog_public' ), $items ) ) );
     94                return hash( 'sha256', wp_json_encode( array( $this->settings->get_all(), VWAO_Multilingual::locale(), VWAO_SEO_Service::get_providers(), get_option( 'blog_public' ), self::configured_languages(), $items ) ) );
     95        }
     96        /** Configured choices are distinct from each item's complete published variants. */
     97        private static function configured_languages() {
     98                $rows = apply_filters( 'vwml_available_languages_v1', array(), 'discovery' );
     99                return is_array( $rows ) ? array_values( array_filter( array_keys( $rows ), static fn( $code ) => is_string( $code ) && preg_match( '/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{2,8})*$/D', $code ) ) ) : array();
     100        }
     101        private function language_lines() {
     102                $codes = self::configured_languages();
     103                if ( ! $codes ) { return array(); }
     104                $lines = array( 'Configured languages: ' . implode( ', ', $codes ), 'Only complete published translations appear in language-specific results.' );
     105                foreach ( $codes as $code ) {
     106                        $lines[] = '- ' . $code . ': ' . esc_url_raw( add_query_arg( 'language', $code, home_url( '/llms.txt' ) ) ); }
     107                return $lines;
    95108        }
    96109
    … …  
    256269                $lines[] = 'Base URL: ' . $site['base_url'];
    257270                $lines[] = 'Language: ' . VWAO_Multilingual::language();
     271                $lines = array_merge( $lines, $this->language_lines() );
    258272                $lines[] = 'Generated (GMT): ' . gmdate( 'c' );
    259273                $lines[] = '';
    … …  
    323337                $lines[] = 'Base URL: ' . $site['base_url'];
    324338                $lines[] = 'Language: ' . VWAO_Multilingual::language();
     339                $lines = array_merge( $lines, $this->language_lines() );
    325340                $lines[] = 'Generated (GMT): ' . gmdate( 'c' );
    326341                $lines[] = '';
    … …  
    435450                $xml  = '<?xml version="1.0" encoding="' . esc_attr( get_bloginfo( 'charset' ) ) . '"?>';
    436451                $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:agent="urn:videowhisper:agentic:1.0">';
     452                $xml .= '<!-- Configured languages: ' . esc_html( implode( ', ', self::configured_languages() ) ) . '; per-URL available_languages lists complete published variants. -->';
    437453
    438454                foreach ( $content as $group ) {
  • videowhisper-agentic-optimization/trunk/includes/class-vwao-multilingual.php

    r3790761 r3791583  
    7474        /** Register rendering hooks. */
    7575        public static function hooks() {
     76                add_filter( 'vwml_sitemap_source_allowed_v1', static function ( $allowed, $post ) {
     77                        if ( false === $allowed ) { return false; }
     78                        $seo = VWAO_SEO_Service::get_post_state( $post );
     79                        return false === ( $seo['noindex'] ?? null ) && untrailingslashit( $seo['canonical'] ?? '' ) === untrailingslashit( get_permalink( $post ) );
     80                }, 10, 2 );
    7681                add_filter(
    7782                        'vwml_use_public_overlay',
  • videowhisper-agentic-optimization/trunk/includes/class-vwao-recovery.php

    r3791079 r3791583  
    2020                add_action( 'shutdown', array( __CLASS__, 'capture' ) );
    2121                add_action( 'vwao_recovery_cleanup', array( __CLASS__, 'prune' ) );
     22                add_filter( 'vwml_legacy_source_id_v1', array( __CLASS__, 'translation_source' ), 10, 4 );
     23        }
     24        /** Resolve only an existing, reviewed local redirect; never guess a target. */
     25        public static function translation_source( $id, $path, $locale = '', $request_path = '' ) {
     26                unset( $locale );
     27                if ( $id || '1' !== get_option( 'vwao_recovery_schema' ) ) { return $id; }
     28                global $wpdb;
     29                foreach ( array_unique( array( self::path( $path ), self::path( $request_path ) ) ) as $candidate ) {
     30                        if ( ! $candidate || self::decision( $candidate ) ) { continue; }
     31                        $r = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE path_hash=%s AND state=%s', $wpdb->prefix . 'vwao_recovery_paths', hash( 'sha256', $candidate ), 'redirect' ), ARRAY_A );
     32                        $p = $r && hash_equals( $r['path'], $candidate ) ? self::target( (int) $r['target_id'] ) : null;
     33                        if ( $p ) { return (int) $p->ID; }
     34                }
     35                return $id;
    2236        }
    2337        public static function settings() {
    … …  
    223237        }
    224238        public static function version( $row ) {
    225                 return hash( 'sha256', wp_json_encode( array( get_current_blog_id(), $row['id'], $row['path'], $row['state'], $row['target_id'], $row['redirect_code'], $row['draft_id'], $row['revision'] ) ) );
     239                // Database rows and report rows use different scalar types. Bind the same
     240                // normalized editorial state in both; traffic counters are not permissions.
     241                return hash( 'sha256', wp_json_encode( array( get_current_blog_id(), (int) $row['id'], (string) $row['path'], (string) $row['state'], (int) $row['target_id'], (int) $row['redirect_code'], (int) $row['draft_id'], (int) $row['revision'] ) ) );
    226242        }
    227243        public static function target( $id ) {
    … …  
    395411                                $p = self::target( $target );
    396412                                $existing = self::existing_id( $r['path'] );
    397                                 $draft = ! empty( $r['draft_id'] ) ? (int) $r['draft_id'] : 0;
    398                                 if ( $draft ) { clean_post_cache( $draft ); }
    399                                 // A linked editable recovery draft is retained, never overwritten or published.
    400                                 $source_conflict = $existing && ( (int) $existing !== $draft || ! self::reusable_draft( $r ) );
     413                                // Existing unpublished drafts remain untouched. A later real public
     414                                // page wins because runtime recovery only runs for an actual 404.
     415                                $source_conflict = $existing && is_post_publicly_viewable( get_post( $existing ) );
    401416                                if ( ! $p || ! in_array( $code, array( 301, 302 ), true ) || $source_conflict || self::decision( $r['path'] ) || self::same_path( get_permalink( $p ), $r['path'] ) ) {
    402417                                        return self::error(); }
  • videowhisper-agentic-optimization/trunk/includes/class-vwao-standalone-seo.php

    r3789597 r3791583  
    116116                add_filter( 'wp_robots', array( __CLASS__, 'robots' ), 20 );
    117117                add_filter( 'wp_sitemaps_posts_query_args', array( __CLASS__, 'sitemap_query' ), 20, 2 );
     118                add_filter( 'posts_where', array( __CLASS__, 'sitemap_where' ), 20, 2 );
    118119        }
    119120
    … …  
    346347                        return $args; }
    347348                $args['has_password'] = false;
    348                 $clauses              = array( 'relation' => 'AND' );
    349                 if ( ! empty( $args['meta_query'] ) ) {
    350                         $clauses[] = $args['meta_query']; }
     349                $args['vwao_sitemap_seo'] = true;
     350                return $args;
     351        }
     352
     353        /** Indexed exclusions avoid multiplying theme metadata rows across six joins. */
     354        public static function sitemap_where( $where, $query ) {
     355                if ( true !== $query->get( 'vwao_sitemap_seo' ) || ! self::active() ) { return $where; }
     356                global $wpdb;
    351357                foreach ( array(
    352                         '_vwao_seo_noindex'                => array( '1' ),
     358                        '_vwao_seo_noindex' => array( '1' ),
    353359                        '_yoast_wpseo_meta-robots-noindex' => array( '1' ),
    354                         '_aioseo_robots_noindex'           => array( '1', 'on', 'yes' ),
    355                         '_seopress_robots_index'           => array( 'yes' ),
     360                        '_aioseo_robots_noindex' => array( '1', 'on', 'yes' ),
     361                        '_seopress_robots_index' => array( 'yes' ),
    356362                ) as $key => $values ) {
    357                         $clauses[] = array(
    358                                 'relation' => 'OR',
    359                                 array(
    360                                         'key'     => $key,
    361                                         'compare' => 'NOT EXISTS',
    362                                 ),
    363                                 array(
    364                                         'key'     => $key,
    365                                         'value'   => $values,
    366                                         'compare' => 'NOT IN',
    367                                 ),
    368                         );
    369                 }
    370                 $clauses[] = array(
    371                         'relation' => 'OR',
    372                         array(
    373                                 'key'     => 'rank_math_robots',
    374                                 'compare' => 'NOT EXISTS',
    375                         ),
    376                         array(
    377                                 'key'     => 'rank_math_robots',
    378                                 'value'   => '"noindex"',
    379                                 'compare' => 'NOT LIKE',
    380                         ),
    381                 );
    382                 // Custom canonical destinations are not advertised as self-canonical sitemap URLs.
    383                 $clauses[]          = array(
    384                         'relation' => 'OR',
    385                         array(
    386                                 'key'     => '_vwao_seo_canonical',
    387                                 'compare' => 'NOT EXISTS',
    388                         ),
    389                         array(
    390                                 'key'     => '_vwao_seo_canonical',
    391                                 'value'   => '',
    392                                 'compare' => '=',
    393                         ),
    394                 );
    395                 $args['meta_query'] = $clauses;
    396                 return $args;
     363                        $holders = implode( ',', array_fill( 0, count( $values ), '%s' ) );
     364                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers and generated placeholder list; values are prepared.
     365                        $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value IN ($holders))", array_merge( array( $key ), $values ) );
     366                }
     367                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers; values are prepared.
     368                $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value LIKE %s)", 'rank_math_robots', '%' . $wpdb->esc_like( '"noindex"' ) . '%' );
     369                // Custom canonical destinations are excluded, including duplicate conflicting metadata.
     370                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table identifiers; values are prepared.
     371                $where .= $wpdb->prepare( " AND NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} vwao_sm WHERE vwao_sm.post_id={$wpdb->posts}.ID AND vwao_sm.meta_key=%s AND vwao_sm.meta_value<>%s)", '_vwao_seo_canonical', '' );
     372                return $where;
    397373        }
    398374
  • videowhisper-agentic-optimization/trunk/languages/videowhisper-agentic-optimization.pot

    r3791079 r3791583  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Agentic Optimization - SEO for AI Agents 1.8.0\n"
     5"Project-Id-Version: Agentic Optimization - SEO for AI Agents 1.9.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/videowhisper-agentic-optimization\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-11-08T13:59:03+00:00\n"
     12"POT-Creation-Date: 2026-11-08T21:05:52+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    695695
    696696#. translators: 1: result cap, 2: max pages, 3: llms list cap.
    697 #: includes/class-vwao-discovery-service.php:250
     697#: includes/class-vwao-discovery-service.php:263
    698698#, php-format
    699699msgid "Use the Search endpoint for discovery, read specific items with Content or Markdown by numeric ID or content slug, including hierarchical paths (e.g., /content/25, /content/item1/item2/item3, or /markdown/item1/item2/item3). All listed endpoints are read-only and expose public content only. Search returns up to %1$d results per request across %2$d pages. This llms.txt file lists up to %3$d items per enabled content type, and pages can be unlimited when that option is enabled."
    700700msgstr ""
    701701
    702 #: includes/class-vwao-discovery-service.php:261
    703 #: includes/class-vwao-discovery-service.php:328
     702#: includes/class-vwao-discovery-service.php:275
     703#: includes/class-vwao-discovery-service.php:343
    704704msgid "Agent Endpoints"
    705705msgstr ""
    706706
    707 #: includes/class-vwao-discovery-service.php:262
    708 #: includes/class-vwao-discovery-service.php:329
     707#: includes/class-vwao-discovery-service.php:276
     708#: includes/class-vwao-discovery-service.php:344
    709709msgid "Discovery Files"
    710710msgstr ""
    711711
    712712#. translators: 1: item cap, 2: word cap.
    713 #: includes/class-vwao-discovery-service.php:318
     713#: includes/class-vwao-discovery-service.php:332
    714714#, php-format
    715715msgid "This expanded context file includes up to %1$d items per enabled content type and up to %2$d words per item to keep context manageable on large sites."
    … …  
    766766
    767767#: includes/class-vwao-multilingual.php:30
    768 #: includes/class-vwao-multilingual.php:108
     768#: includes/class-vwao-multilingual.php:112
    769769msgid "Invalid content language."
    770770msgstr ""
    … …  
    10731073msgstr ""
    10741074
    1075 #: includes/class-vwao-recovery.php:277
     1075#: includes/class-vwao-recovery.php:287
    10761076msgid "Administrator access is required."
    10771077msgstr ""
    10781078
    1079 #: includes/class-vwao-recovery.php:459
     1079#: includes/class-vwao-recovery.php:469
    10801080msgid "The recovery item, destination or permissions changed. Review the current item again."
    10811081msgstr ""
    10821082
    1083 #: includes/class-vwao-recovery.php:463
     1083#: includes/class-vwao-recovery.php:473
    10841084msgid "Traffic Recovery storage is unavailable. Review database access, then save recovery settings to retry setup."
    10851085msgstr ""
  • videowhisper-agentic-optimization/trunk/readme.txt

    r3791079 r3791583  
    108108* Analytics disabled by default
    109109
     110= Multilingual discovery =
     111
     112When Agentic Multilingual Translator is active, discovery indexes list configured language choices separately from each page's complete published variants. Agent-facing translated content retains the original URL and available-language retrieval links. Reviewed local Traffic Recovery redirects can resolve old source slugs for legacy language URLs. Language sitemap entries respect the current original page's SEO indexing and canonical policy.
     113
    110114== Installation ==
    111115
    … …  
    222226== Changelog ==
    223227
     228= 1.9.0 =
     229* Keep core sitemaps responsive on metadata-heavy builder pages with indexed SEO exclusions. Retain unpublished drafts when reviewing a missing-URL redirect.
     230* Discovery indexes list configured languages separately from complete published variants.
     231* Reviewed traffic recovery targets can resolve legacy translated permalinks; sitemap entries respect current source SEO policy.
     232
     233
    224234= 1.8.0 =
    225235* Add opt-in, bounded Traffic Recovery reports, configurable retention and private Site Manager reporting.
  • videowhisper-agentic-optimization/trunk/videowhisper-agentic-optimization.php

    r3791079 r3791583  
    33 * Plugin Name: Agentic Optimization - SEO for AI Agents
    44 * Description: Optional local search SEO editing plus agent-friendly discovery, markdown and read-only public REST/MCP access.
    5  * Version:     1.8.0
     5 * Version:     1.9.0
    66 * Requires at least: 6.9
    77 * Requires PHP: 8.2
    … …  
    1919defined( 'ABSPATH' ) || exit;
    2020
    21 define( 'VWAO_VERSION', '1.8.0' );
     21define( 'VWAO_VERSION', '1.9.0' );
    2222define( 'VWAO_REWRITE_VERSION', '3' );
    2323define( 'VWAO_PLUGIN_FILE', __FILE__ );
Note: See TracChangeset for help on using the changeset viewer.