<?php /** * Yoast Constants * * Defines constants and utility functions for Yoast SEO plugin importer. * * @package SureRank\Inc\Importers * @since 1.1.0 */ namespace SureRank\Inc\Importers\Yoast; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class YoastConstants */ class Constants { /** * Human-readable plugin name. */ public const PLUGIN_NAME = 'Yoast SEO'; /** * Plugin Slug. */ public const PLUGIN_SLUG = 'yoast'; /** * Yoast plugin file path. */ public const PLUGIN_FILE = 'wordpress-seo/wp-seo.php'; /** * Prefix for Yoast meta keys. */ public const META_KEY_PREFIX = '_yoast_wpseo_'; /** * Yoast global robots settings. */ public const GLOBAL_ROBOTS = [ 'noindex' => 'no', 'nofollow' => 'no', 'noarchive' => 'no', ]; /** * Allowed post and term types for import. */ public const NOT_ALLOWED_TYPES = [ 'elementor_library', 'product_shipping_class', ]; /** * Mapping of Yoast robots to SureRank robots. */ public const ROBOTS_MAPPING = [ 'noindex' => 'post_no_index', 'nofollow' => 'post_no_follow', 'noarchive' => 'post_no_archive', ]; public const EXCLUDED_META_KEYS = [ '_yoast_wpseo_primary_category', '_yoast_wpseo_content_score', '_yoast_wpseo_estimated-reading-time-minutes', '_yoast_wpseo_keywordsynonyms', '_yoast_wpseo_focuskeywords', ]; /** * Mapping of Yoast social meta to SureRank social meta. */ private const SOCIAL_MAPPING = [ '_yoast_wpseo_opengraph-title' => [ 'social-title', 'facebook_title' ], '_yoast_wpseo_opengraph-description' => [ 'social-description', 'facebook_description' ], '_yoast_wpseo_opengraph-image' => [ 'social-image-url', 'facebook_image_url' ], '_yoast_wpseo_opengraph-image-id' => [ 'social-image-id', 'facebook_image_id' ], '_yoast_wpseo_twitter-title' => [ '', 'twitter_title' ], '_yoast_wpseo_twitter-description' => [ '', 'twitter_description' ], '_yoast_wpseo_twitter-image' => [ '', 'twitter_image_url' ], '_yoast_wpseo_twitter-image-id' => [ '', 'twitter_image_id' ], ]; /** * Mapping of Yoast placeholders to SureRank placeholders. */ public const PLACEHOLDERS_MAPPING = [ '%%sitename%%' => '%site_name%', '%%modified%%' => '%modified%', '%%date%%' => '%published%', '%%sep%%' => '-', '%%page%%' => '%page%', '%%currenttime%%' => '%currenttime%', '%%currentyear%%' => '%currentyear%', '%%currentmonth%%' => '%currentmonth%', '%%currentday%%' => '%currentday%', '%%currentdate%%' => '%currentdate%', '%%org_name%%' => '%org_name%', '%%org_url%%' => '%org_url%', '%%org_logo%%' => '%org_logo%', '%%name%%' => '%author_name%', '%%post_url%%' => '%post_url%', '%%title%%' => '%title%', '%%excerpt%%' => '%excerpt%', '%%term_title%%' => '%term_title%', '%%term_description%%' => '%term_description%', '%%sitedesc%%' => '%tagline%', ]; /** * Mapping for global title and description settings. */ private const TITLE_DESC_MAPPING = [ 'title-home-wpseo' => 'home_page_title', 'metadesc-home-wpseo' => 'home_page_description', 'open_graph_frontpage_title' => 'home_page_facebook_title', 'open_graph_frontpage_desc' => 'home_page_facebook_description', 'open_graph_frontpage_image' => 'home_page_facebook_image_url', ]; private const OG_SETTINGS_MAPPING = [ 'twitter_card_type' => 'twitter_card_type', 'twitter_site' => 'twitter_profile_username', 'facebook_site' => 'facebook_page_url', 'facebook_default_image' => 'fallback_image', ]; /** * Mapping for archive settings. */ private const ARCHIVE_SETTINGS_MAPPING = [ 'disable-author' => 'author_archive', 'disable-date' => 'date_archive', 'noindex-author-wpseo' => 'noindex_paginated_pages', ]; /** * Mapping for sitemap settings. */ private const SITEMAP_MAPPING = [ 'enable_xml_sitemap' => 'enable_xml_sitemap', ]; /** * Mapping for robot settings. */ private const ROBOT_KEYS_MAPPING = [ 'noindex-post' => 'post', 'noindex-page' => 'page', 'noindex-attachment' => 'attachment', 'noindex-tax-category' => 'category', 'noindex-tax-post_tag' => 'post_tag', 'noindex-tax-post_format' => 'post_format', 'noindex-author-wpseo' => 'author', 'nodeindx-date-archive' => 'date_archive', ]; /** * Mapping for social settings. * Term social settings are mapped to SureRank social meta. */ public const TERM_SOCIAL_MAPPING = [ 'wpseo_opengraph-title' => [ 'social-title-tax', 'facebook_title' ], 'wpseo_opengraph-description' => [ 'social-description-tax', 'facebook_description' ], 'wpseo_opengraph-image' => [ 'social-image-url-tax', 'facebook_image_url' ], 'wpseo_opengraph-image-id' => [ 'social-image-id-tax', 'facebook_image_id' ], 'wpseo_twitter-title' => [ '', 'twitter_title' ], 'wpseo_twitter-description' => [ '', 'twitter_description' ], 'wpseo_twitter-image' => [ '', 'twitter_image_url' ], 'wpseo_twitter-image-id' => [ '', 'twitter_image_id' ], ]; public const SEPARATOR_MAPPING = [ 'sc-dash' => '-', 'sc-ndash' => '–', 'sc-mdash' => '—', 'sc-middot' => '·', 'sc-bull' => '•', 'sc-star' => '*', 'sc-smstar' => '⋆', 'sc-pipe' => '|', 'sc-tilde' => '~', 'sc-laquo' => '«', 'sc-raquo' => '»', 'sc-lt' => '>', 'sc-gt' => '<', ]; /** * Get Yoast meta data for a specific post or term. * * @param int $id Post or Term ID. * @param bool $is_taxonomy Whether the ID is for a taxonomy term. * @param string $type Type of taxonomy if applicable (default: 'category'). * @return array<string, mixed> Yoast meta data. */ public static function yoast_meta_data( $id, $is_taxonomy = false, $type = 'category' ) { if ( $is_taxonomy ) { $term_meta = get_option( 'wpseo_taxonomy_meta', [] ); $term_meta = $term_meta[ $type ][ $id ] ?? []; $data = $term_meta; } else { $data = get_post_meta( $id ); } $yoast_global_settings = get_option( 'wpseo_titles', [] ); if ( ! is_wp_error( $data ) ) { foreach ( $yoast_global_settings as $key => $value ) { // If the key is not already present in $data, add it. if ( ! array_key_exists( $key, $data ) ) { $data[ $key ] = $value; } } } return $data; } /** * Replace Yoast placeholders with SureRank placeholders in a given value. * * @param string|array<string> $value The value containing placeholders to replace. * @param string|null $separator Optional separator to replace the %%sep%% placeholder. * @return string The value with placeholders replaced. */ public static function replace_placeholders( $value, ?string $separator = null ) { if ( is_array( $value ) ) { $replaced = array_map( static fn( $item) => self::replace_placeholders( $item, $separator ), $value ); return implode( ', ', $replaced ); } if ( ! is_string( $value ) ) { return $value; } $placeholders = self::PLACEHOLDERS_MAPPING; // If the value contains %%sep%% and a separator is provided, override the default %%sep%% mapping. if ( $separator !== null && strpos( $value, '%%sep%%' ) !== false ) { $placeholders['%%sep%%'] = self::SEPARATOR_MAPPING[ $separator ] ?? '-'; } // Split the string into parts based on %%...%% patterns. preg_match_all( '/%%[^%]+%%|[^%]+/', $value, $matches ); $result = ''; foreach ( $matches[0] as $part ) { // Check if the part is a placeholder (starts and ends with %%). if ( preg_match( '/^%%[^%]+%%$/', $part ) ) { // Replace placeholder if it exists in PLACEHOLDERS_MAPPING else SKIP. if ( isset( $placeholders[ $part ] ) ) { $result .= $placeholders[ $part ]; } } else { // Keep the part as is (either non-placeholder text or unmatched placeholder). $result .= $part; } } return $result; } /** * Get filtered title and description mapping. * * @return array<string, string> Filtered title and description mapping. */ public static function get_title_desc_mapping() { return apply_filters( 'surerank_yoast_title_desc_mapping', self::TITLE_DESC_MAPPING ); } /** * Get sitemap mapping with filter. * * @return array<string, string> Sitemap mapping array. */ public static function get_sitemap_mapping() { return apply_filters( 'surerank_yoast_sitemap_mapping', self::SITEMAP_MAPPING ); } /** * Get robots key mapping with filter. * * @return array<string, string> Robots key mapping array. */ public static function get_robots_key_mapping() { return apply_filters( 'surerank_yoast_robots_key_mapping', self::ROBOT_KEYS_MAPPING ); } /** * Get archive settings mapping with filter. * * @return array<string, string> Archive settings mapping array. */ public static function get_archive_settings_mapping() { return apply_filters( 'surerank_yoast_archive_settings_mapping', self::ARCHIVE_SETTINGS_MAPPING ); } /** * Get social mapping with filter. * * @return array<string, array<int, string>> Social mapping array. */ public static function get_social_mapping() { /** * Filter the Yoast social mapping. * * @since 1.4.0 * * @param array<string, array<int, string>> $mapping The default social mapping. */ return apply_filters( 'surerank_yoast_social_mapping', self::SOCIAL_MAPPING ); } /** * Get social settings mapping with filter. * * @return array<string, string> Social settings mapping array. */ public static function get_social_settings_mapping() { /** * Filter the Yoast social settings mapping. * * @since 1.4.0 * * @param array<string, string> $mapping The default social settings mapping. */ return apply_filters( 'surerank_yoast_social_settings_mapping', self::OG_SETTINGS_MAPPING ); } /** * Get term IDs from Yoast taxonomy meta. * * @param array<string, object> $taxonomies_objects Array of taxonomy objects. * @return array{term_ids: array<int>, total_items: int} Array containing term IDs and total count. */ public static function get_term_ids( $taxonomies_objects ) { $taxonomy_meta = get_option( 'wpseo_taxonomy_meta', [] ); $term_ids = []; $total_terms = 0; if ( empty( $taxonomy_meta ) || ! is_array( $taxonomy_meta ) ) { return [ 'term_ids' => [], 'total_items' => 0, ]; } // Extract all term IDs from relevant taxonomies. $all_term_ids = []; $taxonomies = []; foreach ( $taxonomy_meta as $taxonomy => $terms ) { // Ensure the taxonomy is public and in $taxonomies_objects. if ( ! isset( $taxonomies_objects[ $taxonomy ] ) || ! ( property_exists( $taxonomies_objects[ $taxonomy ], 'public' ) && $taxonomies_objects[ $taxonomy ]->public ) ) { continue; } if ( ! is_array( $terms ) ) { continue; } $taxonomies[] = $taxonomy; foreach ( $terms as $term_id => $meta ) { $all_term_ids[] = (int) $term_id; } } if ( empty( $all_term_ids ) || empty( $taxonomies ) ) { return [ 'term_ids' => [], 'total_items' => 0, ]; } // Use WP_Term_Query to fetch valid terms and filter out migrated ones. $term_query = new \WP_Term_Query( [ 'taxonomy' => $taxonomies, 'include' => array_unique( $all_term_ids ), 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query [ 'key' => 'surerank_migration', 'compare' => 'NOT EXISTS', ], ], 'fields' => 'ids', 'hide_empty' => false, ] ); if ( ! empty( $term_query->terms ) ) { $term_ids = array_map( 'intval', $term_query->terms ); $total_terms = count( $term_ids ); } return [ 'term_ids' => $term_ids, 'total_items' => $total_terms, ]; } }