ours. strtr() prefers the longest key, which is what * keeps `%%category%%` from being read as `%` + `%category%` + `%`. * * @return array */ public static function map(): array { return array( // Yoast (%%var%%), also used by SEOPress in its templates. '%%title%%' => '%title%', '%%page_title%%' => '%title%', '%%post_title%%' => '%title%', '%%sitename%%' => '%sitename%', '%%sitedesc%%' => '%tagline%', '%%sep%%' => '%sep%', '%%excerpt%%' => '%excerpt%', '%%excerpt_only%%' => '%excerpt%', '%%category%%' => '%category%', '%%primary_category%%' => '%category%', '%%category_title%%' => '%term%', '%%tag%%' => '%tags%', '%%name%%' => '%author%', '%%user_description%%' => '%term_description%', '%%date%%' => '%date%', '%%modified%%' => '%modified%', '%%currentyear%%' => '%currentyear%', '%%currentdate%%' => '%currentdate%', '%%currentmonth%%' => '%currentdate%', '%%currentday%%' => '%currentdate%', '%%page%%' => '%page%', '%%pagenumber%%' => '%page%', '%%pagetotal%%' => '%page%', '%%term_title%%' => '%term%', '%%tax_title%%' => '%term%', '%%term_description%%' => '%term_description%', '%%tag_description%%' => '%term_description%', '%%category_description%%' => '%term_description%', '%%pt_single%%' => '%post_type%', '%%pt_plural%%' => '%post_type%', '%%searchphrase%%' => '%search_query%', '%%parent_title%%' => '%parent_title%', // Rank Math / SEOPress / AIOSEO (%var%). '%title%' => '%title%', '%post_title%' => '%title%', '%seo_title%' => '%title%', '%page_title%' => '%title%', '%sitename%' => '%sitename%', '%site_title%' => '%sitename%', '%sep%' => '%sep%', '%separator%' => '%sep%', '%sitedesc%' => '%tagline%', '%tagline%' => '%tagline%', '%site_description%' => '%tagline%', '%excerpt%' => '%excerpt%', '%excerpt_only%' => '%excerpt%', '%post_excerpt%' => '%excerpt%', '%seo_description%' => '%excerpt%', '%wc_shortdesc%' => '%excerpt%', '%category%' => '%category%', '%categories%' => '%category%', '%primary_category%' => '%category%', '%category_title%' => '%term%', '%tags%' => '%tags%', '%tag%' => '%tags%', '%author%' => '%author%', '%post_author%' => '%author%', '%name%' => '%author%', '%date%' => '%date%', '%post_date%' => '%date%', '%modified%' => '%modified%', '%post_modified%' => '%modified%', '%currentyear%' => '%currentyear%', '%current_year%' => '%currentyear%', '%currentdate%' => '%currentdate%', '%current_date%' => '%currentdate%', '%currentmonth%' => '%currentdate%', '%currentday%' => '%currentdate%', '%page%' => '%page%', '%pagenumber%' => '%page%', '%page_number%' => '%page%', '%term%' => '%term%', '%term_title%' => '%term%', '%tax_title%' => '%term%', '%taxonomy_title%' => '%term%', '%term_description%' => '%term_description%', '%taxonomy_description%' => '%term_description%', '%pt_single%' => '%post_type%', '%pt_plural%' => '%post_type%', '%post_type_name%' => '%post_type%', '%search_query%' => '%search_query%', '%search_term%' => '%search_query%', '%count%' => '%count%', '%parent_title%' => '%parent_title%', // All in One SEO (#var) and The SEO Framework / Genesis. '#post_title' => '%title%', '#page_title' => '%title%', '#site_title' => '%sitename%', '#blog_title' => '%sitename%', '#site_description' => '%tagline%', '#tagline' => '%tagline%', '#separator_sa' => '%sep%', '#separator' => '%sep%', '#post_excerpt' => '%excerpt%', '#post_content' => '%excerpt%', '#current_year' => '%currentyear%', '#current_date' => '%currentdate%', '#author_name' => '%author%', '#post_date' => '%date%', '#taxonomy_title' => '%term%', '#taxonomy_description' => '%term_description%', '#archive_title' => '%term%', '#search_term' => '%search_query%', '#page_number' => '%page%', '#categories' => '%category%', '#tags' => '%tags%', ); } /** * Every variable some resolver in this plugin fills: post templates * (SeoModule::fill) plus archive templates (Archives). One list, so an * importer cannot keep a variable the front end then prints literally. * * @return array */ public static function known(): array { return array_fill_keys( array( '%title%', '%sitename%', '%sep%', '%tagline%', '%excerpt%', '%category%', '%author%', '%date%', '%modified%', '%currentyear%', '%currentdate%', '%page%', '%tags%', '%term%', '%term_description%', '%taxonomy%', '%post_type%', '%search_query%', '%count%', '%pagenumber%', '%parent_title%' ), true ); } /** * Translate one template string. Idempotent: running it over a value this * plugin already produced changes nothing. */ public static function translate( string $value ): string { if ( '' === trim( $value ) ) { return ''; } $value = strtr( $value, self::map() ); $known = self::known(); $value = (string) preg_replace_callback( '/%%?[a-z_0-9\-]+(?:\([^)]*\))?%%?|#[a-z_]+/i', static fn( array $m ): string => isset( $known[ $m[0] ] ) ? $m[0] : '', $value ); return trim( (string) preg_replace( '/\s{2,}/', ' ', $value ) ); } /** * A separator constant into the character it stands for. Yoast and Rank * Math both store a key ('sc-pipe', 'sep-pipe') rather than the glyph. */ public static function separator( string $value ): string { $map = array( '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' => '>', 'sep-hyphen' => '-', 'sep-dash' => '–', 'sep-emdash' => '—', 'sep-pipe' => '|', 'sep-bullet' => '•', 'sep-raquo' => '»', 'sep-laquo' => '«', 'sep-mid' => '·', 'sep-colon' => ':', 'sep-tilde' => '~', 'sep-star' => '*', 'sep-gt' => '>', ); $value = trim( $value ); if ( isset( $map[ $value ] ) ) { return $map[ $value ]; } // An unknown key ('sc-something') is not a separator glyph — better no // separator than three letters in every title on the site. return preg_match( '/^(sc|sep)-/', $value ) ? '' : mb_substr( $value, 0, 3 ); } }