array( 'Article', 'NewsArticle', 'BlogPosting', 'TechArticle', 'ScholarlyArticle', 'AnalysisNewsArticle', 'ReportageNewsArticle', 'OpinionNewsArticle' ), 'NewsArticle' => array( 'Article', 'NewsArticle', 'BlogPosting' ), 'BlogPosting' => array( 'Article', 'NewsArticle', 'BlogPosting' ), 'Product' => array( 'Product' ), 'Recipe' => array( 'Recipe' ), 'Event' => array( 'Event' ), 'LocalBusiness' => array( 'LocalBusiness', 'Organization' ), 'Organization' => array( 'Organization' ), 'Person' => array( 'Person' ), 'FAQPage' => array( 'FAQPage' ), 'HowTo' => array( 'HowTo' ), 'Review' => array( 'Review' ), 'Course' => array( 'Course' ), 'VideoObject' => array( 'VideoObject' ), 'ImageObject' => array( 'ImageObject' ), 'BreadcrumbList' => array( 'BreadcrumbList' ), 'Service' => array( 'Service' ), 'WebSite' => array( 'WebSite' ), 'WebPage' => array( 'WebPage', 'CollectionPage', 'ProfilePage', 'SearchResultsPage', 'AboutPage', 'ContactPage', 'FAQPage', 'ItemPage' ), ); /** * Types that Yoast should always keep unless Schema AI explicitly provides them. * * @var array */ private $yoast_protected_types = array( 'WebSite', 'WebPage', 'BreadcrumbList', 'SearchAction' ); /** * Suppress all Yoast SEO JSON-LD output when Schema AI is active. * * @param array $data The Yoast schema graph data. * @return array Empty array — Schema AI owns all schema output. */ public function filter_yoast_schema( $data ) { return array(); } /** * Check if Schema AI has any schema for a given post. * * Queries the schema_ai_schemas custom table for any rows matching * the given post ID. * * @param int $post_id The WordPress post ID. * @return bool True if at least one schema exists. */ public function has_schema_ai_schema( $post_id ) { global $wpdb; $table = esc_sql( $wpdb->prefix . 'schema_ai_schemas' ); // Verify the table exists before querying. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; no WP API alternative exists. $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); if ( ! $table_exists ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; no WP API alternative exists. $count = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is sanitized via esc_sql() and not user input. "SELECT COUNT(*) FROM {$table} WHERE post_id = %d AND validation_status != %s", $post_id, 'invalid' ) ); return absint( $count ) > 0; } /** * Get all Schema AI schema types for a given post. * * @param int $post_id The WordPress post ID. * @return array Array of schema_type strings (e.g. ['Article', 'FAQPage']). */ public function get_schema_ai_types( $post_id ) { global $wpdb; $table = esc_sql( $wpdb->prefix . 'schema_ai_schemas' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; no WP API alternative exists. $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); if ( ! $table_exists ) { return array(); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; no WP API alternative exists. $results = $wpdb->get_col( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is sanitized via esc_sql() and not user input. "SELECT schema_type FROM {$table} WHERE post_id = %d AND validation_status != %s", $post_id, 'invalid' ) ); return ! empty( $results ) ? $results : array(); } /** * Determine whether a Yoast schema type should be disabled. * * Checks if the Yoast type conflicts with any of the Schema AI types. * Protected types (WebSite, WebPage, BreadcrumbList) are only removed * if Schema AI explicitly provides that same type. * * @param string $yoast_type The @type from a Yoast schema piece. * @param array $schema_ai_types Array of Schema AI schema types for the post. * @return bool True if the Yoast schema piece should be removed. */ public function should_disable_yoast_schema( $yoast_type, $schema_ai_types ) { if ( empty( $yoast_type ) ) { return false; } // For protected types, only remove if Schema AI explicitly has the same type. if ( in_array( $yoast_type, $this->yoast_protected_types, true ) ) { return in_array( $yoast_type, $schema_ai_types, true ); } // For other types, check against the alias map. foreach ( $schema_ai_types as $ai_type ) { if ( isset( $this->type_aliases[ $ai_type ] ) ) { if ( in_array( $yoast_type, $this->type_aliases[ $ai_type ], true ) ) { return true; } } // Direct match. if ( $yoast_type === $ai_type ) { return true; } } return false; } /** * Check if Yoast SEO is active. * * @return bool True if the WPSEO_VERSION constant is defined. */ public function is_yoast_active() { return defined( 'WPSEO_VERSION' ); } }