rendering. */ class SeoMetaSynchronizer { /** * Register meta observers. */ public static function register(): void { if ( ! self::has_supported_plugin() ) { return; } add_action( 'added_post_meta', array( self::class, 'handle_post_meta_change' ), 15, 3 ); add_action( 'updated_post_meta', array( self::class, 'handle_post_meta_change' ), 15, 3 ); add_action( 'deleted_post_meta', array( self::class, 'handle_post_meta_change' ), 15, 3 ); add_action( 'added_term_meta', array( self::class, 'handle_term_meta_change' ), 15, 3 ); add_action( 'updated_term_meta', array( self::class, 'handle_term_meta_change' ), 15, 3 ); add_action( 'deleted_term_meta', array( self::class, 'handle_term_meta_change' ), 15, 3 ); } /** * Handle Parallax post meta updates and trigger sync. * * @param int $meta_id Meta row ID. * @param int $post_id Post ID. * @param string $meta_key Meta key. */ public static function handle_post_meta_change( $meta_id, $post_id, $meta_key ): void { if ( ! self::is_parallax_post_key( (string) $meta_key ) ) { return; } self::sync_post( (int) $post_id ); } /** * Handle Parallax term meta updates and trigger sync. * * @param int $meta_id Meta row ID. * @param int $term_id Term ID. * @param string $meta_key Meta key. */ public static function handle_term_meta_change( $meta_id, $term_id, $meta_key ): void { if ( ! self::is_parallax_term_key( (string) $meta_key ) ) { return; } self::sync_term( (int) $term_id ); } /** * Sync post SEO metadata to the active SEO plugin. * * @param int $post_id Post ID. */ private static function sync_post( int $post_id ): void { $source = self::preferred_source(); if ( ! $source ) { return; } $title = self::normalize_value( get_post_meta( $post_id, SeoMetaFields::META_TITLE_KEY, true ) ); $desc = self::normalize_value( get_post_meta( $post_id, SeoMetaFields::META_DESC_KEY, true ) ); OriginalSnapshot::write_seo_meta_for_post( $post_id, $title, $desc, $source ); } /** * Sync term SEO metadata to the active SEO plugin. * * @param int $term_id Term ID. */ private static function sync_term( int $term_id ): void { $source = self::preferred_source(); if ( ! $source ) { return; } $taxonomy = self::get_term_taxonomy( $term_id ); if ( ! $taxonomy ) { return; } $title = self::normalize_value( get_term_meta( $term_id, SeoMetaFields::TERM_META_TITLE_KEY, true ) ); $desc = self::normalize_value( get_term_meta( $term_id, SeoMetaFields::TERM_META_DESC_KEY, true ) ); OriginalSnapshot::write_seo_meta_for_term( $term_id, $taxonomy, $title, $desc, $source ); } /** * Normalize a meta value to a trimmed string or null. * * @param mixed $value Raw meta value. * @return string|null */ private static function normalize_value( $value ): ?string { if ( ! is_scalar( $value ) && ! ( is_object( $value ) && method_exists( $value, '__toString' ) ) ) { return null; } $string = trim( (string) $value ); return '' === $string ? null : $string; } /** * Determine which SEO plugin is active. * * @return string|null */ private static function preferred_source(): ?string { if ( defined( 'WPSEO_VERSION' ) ) { return 'yoast'; } if ( defined( 'RANK_MATH_VERSION' ) ) { return 'rank-math'; } if ( defined( 'SEOPRESS_VERSION' ) ) { return 'seopress'; } if ( defined( 'AIOSEO_VERSION' ) ) { return 'aioseo'; } return null; } /** * Check if a supported SEO plugin is active. * * @return bool */ private static function has_supported_plugin(): bool { return self::preferred_source() !== null; } /** * Determine if the meta key belongs to Parallax for posts. * * @param string $meta_key Meta key to check. * @return bool */ private static function is_parallax_post_key( string $meta_key ): bool { return in_array( $meta_key, array( SeoMetaFields::META_TITLE_KEY, SeoMetaFields::META_DESC_KEY, ), true ); } /** * Determine if the meta key belongs to Parallax for terms. * * @param string $meta_key Meta key to check. * @return bool */ private static function is_parallax_term_key( string $meta_key ): bool { return in_array( $meta_key, array( SeoMetaFields::TERM_META_TITLE_KEY, SeoMetaFields::TERM_META_DESC_KEY, ), true ); } /** * Get taxonomy slug for a term if available. * * @param int $term_id Term ID. * @return string|null */ private static function get_term_taxonomy( int $term_id ): ?string { $term = get_term( $term_id ); if ( $term instanceof \WP_Term ) { return (string) $term->taxonomy; } return null; } }