Skip to content

Instantly share code, notes, and snippets.

@RavanH
Last active April 6, 2024 23:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RavanH/522d26f2dd2801d8b1b01cdcc5e72219 to your computer and use it in GitHub Desktop.
Save RavanH/522d26f2dd2801d8b1b01cdcc5e72219 to your computer and use it in GitHub Desktop.
Rank Math Polylang compatibility
<?php
add_action(
'pll_init',
function() {
defined( 'RANK_MATH_VERSION' ) && class_exists( 'PLL_Integrations' ) && PLL_Integrations::instance()->rankmath = new PLL_RankMath();
}
);
/**
* Manages the compatibility with the Rank Math plugin
* https://rankmath.com/kb/polylang-compatibility/
*
* alternatif: https://github.com/polylang/polylang/pull/373/commits/5eb7014f3c567fd75886f4121f9db2ea1dd9f1de
*
*/
class PLL_RankMath {
/**
* Add specific filters and actions
*/
public function __construct() {
add_action( 'wp_loaded', [ $this, 'translate_options_keys' ] );
if ( PLL() instanceof PLL_Frontend ) {
// Filters sitemap queries to remove inactive language or to get
// one sitemap per language when using multiple domains or subdomains
// because Rank Math does not accept several domains or subdomains in one sitemap
add_filter( 'rank_math/sitemap/post_count/join', [ $this, 'sitemap_join_clause' ], 10, 2);
add_filter( 'rank_math/sitemap/post_count/where', [ $this, 'sitemap_where_clause' ], 10, 2);
add_filter( 'rank_math/sitemap/get_posts/join', [ $this, 'sitemap_join_clause' ], 10, 2 );
add_filter( 'rank_math/sitemap/get_posts/where', [ $this, 'sitemap_where_clause' ], 10, 2 );
add_filter( 'rank_math/sitemap/enable_caching', '__return_false' ); // Disable cache!
if ( PLL()->options['force_lang'] > 1 ) {
add_filter( 'home_url', [ $this, 'home_url' ], 10, 2 );
} else {
// Get all terms in all languages when the language is set from the content or directory name
add_filter( 'get_terms_args', [ $this, 'update_term_query_args' ] );
// Include links to homepage of all languages.
add_filter( 'rank_math/sitemap/exclude_post_type', [ $this, 'update_sitemap_contents' ], 0 , 2 );
}
// Breadcrumb translation strings.
add_filter( 'rank_math/frontend/breadcrumb/strings', [ $this, 'breadcrumb_strings' ] );
add_filter( 'pll_home_url_white_list', [ $this, 'white_list_home_url' ] );
add_filter( 'rank_math/frontend/canonical', [ $this, 'home_canonical' ], 10, 1 );
add_filter( 'rank_math/opengraph/facebook', [ $this, 'alt_locales' ], 10 );
} else {
// Copy post metas
add_filter( 'pll_copy_post_meta', [ $this, 'sync_post_metas' ], 10, 4 );
// translate post metas
add_filter( 'pll_translate_post_meta', [ $this, 'translate_post_meta' ], 10, 3 );
// Export post metas
add_filter( 'pll_post_metas_to_export', [ $this, 'export_post_metas' ], 10, 1 );
}
}
/**
* Filters the breadcrumb public strings.
*
* @param array $strings Empty array. Bug https://github.com/rankmath/seo-by-rank-math/issues/270
* @return array modified list of arguments
*/
public function breadcrumb_strings( $strings ) {
$strings['prefix'] = pll__( RankMath\Helper::get_settings( 'general.breadcrumbs_prefix' ) );
$strings['home'] = pll__( RankMath\Helper::get_settings( 'general.breadcrumbs_home_label' ) );
$strings['home_link'] = pll_home_url( pll_current_language() );
$strings['error404'] = pll__( RankMath\Helper::get_settings( 'general.breadcrumbs_404_label' ) );
return $strings;
}
/**
* Register options keys for translation.
*
* @return void
*/
public function translate_options_keys() {
// @TODO clear cache!
// keys for rank-math-options-general option
$keys = [
'content_ai_country',
'content_ai_tone',
'content_ai_audience',
'content_ai_language',
'toc_block_title',
];
new PLL_Translate_Option( 'rank-math-options-general', array_fill_keys( $keys, 1 ), [ 'context' => 'plugins/seo-by-rank-math' ] );
// Keys for rank-math-options-titles
$keys = [
'knowledgegraph_name',
'pt_post_title',
'pt_post_description',
'pt_post_default_snippet_*',
'pt_page_title',
'pt_page_description',
'pt_page_default_snippet_*',
'pt_attachment_title',
'pt_attachment_description',
'pt_attachment_default_snippet_*',
'pt_product_title',
'pt_product_description',
'pt_product_default_snippet_*',
];
new PLL_Translate_Option( 'rank-math-options-titles', array_fill_keys( $keys, 1 ), [ 'context' => 'plugins/seo-by-rank-math' ] );
}
/**
* Updates the home and stylesheet URLs when using multiple domains or subdomains.
*
* @param string $url
* @param string $path
* @return $url
*/
public function home_url( $url, $path ) {
$uri = empty( $path ) ? ltrim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' ) : $path;
if ( 'sitemap_index.xml' === $uri || preg_match( '#([^/]+?)-sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $uri ) ) {
$url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
}
return $url;
}
/**
* Get the active languages.
*
* @return array list of active language slugs, empty if all languages are active
*/
protected function get_active_languages() {
$languages = PLL()->model->get_languages_list();
if ( wp_list_filter( $languages, [ 'active' => false ] ) ) {
return wp_list_pluck( wp_list_filter( $languages, [ 'active' => false ], 'NOT' ), 'slug' );
}
return [];
}
/**
* Modifies the sql request for posts sitemaps
* Only when using multiple domains or subdomains or if some languages are not active
*
* @param string $sql JOIN clause
* @param string $post_type
* @return string
*/
public function sitemap_join_clause( $sql, $post_type ) {
return pll_is_translated_post_type( $post_type ) ? $sql . PLL()->model->post->join_clause( 'p' ) : $sql;
}
/**
* Modifies the sql request for posts sitemaps
* Only when using multiple domains or subdomains or if some languages are not active
*
* @param string $sql WHERE clause
* @param string $post_type
* @return string
*/
public function sitemap_where_clause( $sql, $post_type ) {
if ( ! pll_is_translated_post_type( $post_type ) ) {
return $sql;
}
if ( PLL()->options['force_lang'] > 1 && PLL()->curlang instanceof PLL_Language ) {
return $sql . PLL()->model->post->where_clause( PLL()->curlang );
}
$languages = $this->get_active_languages();
if ( empty( $languages ) ) { // Empty when all languages are active.
$languages = pll_languages_list();
}
return $sql . PLL()->model->post->where_clause( $languages );
}
/**
* When the language is set from the content or directory name, the language filter (and inactive languages) need to be removed for the taxonomy sitemaps.
*
* @param array $args get_terms arguments
* @return array modified list of arguments
*/
public function update_term_query_args( $args ) {
if ( isset( $GLOBALS['wp_query']->query['sitemap'] ) ) {
$args['lang'] = implode( ',', $this->get_active_languages() );
}
return $args;
}
/**
* A way to apply rank_math/sitemap/{$type}_content for all indexable post types.
*
* Updates homepage and archive pages to include links to the active languages.
* Always returns $excluded without altering it's value.
*
* @param $exclude
* @param $type
* @return mixed
*/
public function update_sitemap_contents( $exclude, $type ) {
if ( pll_is_translated_post_type( $type ) && ( 'post' !== $type || ! get_option( 'page_on_front' ) ) ) {
// Include post, post type archives, and the homepages in all languages to the sitemap when the front page displays posts get_ogp_alternate_languages() !
add_action( "rank_math/sitemap/{$type}_content", function() use( $type ) {
$generator = new RankMath\Sitemap\Generator();
$post_type_obj = get_post_type_object( $type );
$languages = wp_list_filter( PLL()->model->get_languages_list(), [ 'active' => false ], 'NOT' );
$mod = RankMath\Sitemap\Sitemap::get_last_modified_gmt( $type );
$output = '';
if ( 'page' === $type ) {
if ( ! empty( PLL()->options['hide_default'] ) ) {
// The home url is of course already added by WPSEO.
$languages = wp_list_filter( $languages, [ 'slug' => pll_default_language() ], 'NOT' );
}
foreach ( $languages as $lang ) {
error_log( 'adding for ' . $generator->sitemap_url([
'loc' => pll_home_url( $lang->slug ),
'mod' => $mod
]) );
$output .= $generator->sitemap_url([
'loc' => pll_home_url( $lang->slug ),
'mod' => $mod
]);
}
} elseif ( $post_type_obj->has_archive ) {
// Exclude cases where a post type archive is attached to a page (ex: WooCommerce).
$slug = true === $post_type_obj->has_archive ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive;
if ( ! wpcom_vip_get_page_by_path( $slug ) ) {
// The post type archive in the current language is already added by WPSEO.
$languages = wp_list_filter( $languages, [ 'slug' => pll_current_language() ], 'NOT' );
foreach ( $languages as $lang ) {
PLL()->curlang = $lang; // Switch the language to get the correct archive link.
$output .= $generator->sitemap_url([
'loc' => pll_home_url( $lang->slug ),
'mod' => $mod
]);
}
}
}
return $output;
});
}
return $exclude;
}
/**
* Include language code in the canonical URL.
*
* @param string $canonical The canonical URL.
*
* @return mixed|string
*/
public function home_canonical( $canonical ) {
if ( ! is_home() ) {
return $canonical;
}
$path = ltrim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' );
return $canonical . $path;
}
/**
* Filters home url.
*
* @param array $arr
* @return array
*/
public function white_list_home_url( $arr ) {
return array_merge( $arr, [ [ 'file' => 'seo-by-rank-math' ] ] );
}
/**
* Updates OpenGraph meta output by adding support for translations.
*
* @return void
*/
public function alt_locales() {
$og = new RankMath\OpenGraph\OpenGraph();
$og->network = 'facebook';
foreach ( $this->update_ogp_alternate_languages() as $lang ) {
$og->tag('og:locale:alternate', $lang );
}
}
/**
* Get alternate language codes for Opengraph.
*
* @return string[]
*/
protected function update_ogp_alternate_languages() {
$alternates = [];
foreach ( PLL()->model->get_languages_list() as $language ) {
if ( isset( PLL()->curlang ) && PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) {
$alternates[] = $language->facebook;
}
}
// There is a risk that 2 languages have the same Facebook locale. So let's make sure to output each locale only once.
return array_unique( $alternates );
}
/**
* Synchronizes or copies the metas.
*
* @param $metas
* @param $sync
* @param $from
* @param $to
* @return array
*/
public function sync_post_metas( $metas, $sync, $from, $to ) {
if ( ! $sync ) {
$metas = array_merge( $metas, $this->translatable_meta_keys() );
// Copy image URLS
$metas[] = 'rank_math_facebook_image';
$metas[] = 'rank_math_facebook_image_id';
$metas[] = 'rank_math_twitter_use_facebook';
$metas[] = 'rank_math_twitter_image';
$metas[] = 'rank_math_twitter_image_id';
$metas[] = 'rank_math_robots';
}
$taxonomies = get_taxonomies([
'hierarchical' => true,
'public' => true,
]);
$sync_taxonomies = PLL()->sync->taxonomies->get_taxonomies_to_copy( $sync, $from, $to );
$taxonomies = array_intersect( $taxonomies, $sync_taxonomies );
foreach ( $taxonomies as $taxonomy ) {
$metas[] = 'rank_math_primary_' . $taxonomy;
}
return $metas;
}
/**
* Translate the primary term during the synchronization process
*
* @param int $value Meta value.
* @param string $key Meta key.
* @param string $lang Language of target.
*
* @return int
*/
public function translate_post_meta( $value, $key, $lang ) {
if ( ! RankMath\Helpers\Str::starts_with( 'rank_math_primary_', $key) ) {
return $value;
}
$taxonomy = str_replace( 'rank_math_primary_', '', $key );
if ( ! PLL()->model->is_translated_taxonomy( $taxonomy ) ) {
return $value;
}
return pll_get_term( $value, $lang );
}
/**
* Meta key with translatable values.
*
* @return string[]
*/
private function translatable_meta_keys() {
return [
'rank_math_title',
'rank_math_description',
'rank_math_facebook_title',
'rank_math_facebook_description',
'rank_math_twitter_title',
'rank_math_twitter_description',
'rank_math_focus_keyword',
];
}
/**
* Rank math translatable metas to export.
*
* @param array $metas
*
* @return string[]
*/
public function export_post_metas( $metas ) {
$rm_metas = array_fill_keys( $this->translatable_meta_keys(), 1 );
return array_merge( $metas, $rm_metas );
}
}
@RavanH
Copy link
Author

RavanH commented Mar 26, 2024

Updated to include a dynamic rank_math/frontend/breadcrumb/strings filter.

@RavanH
Copy link
Author

RavanH commented Mar 27, 2024

Updated/fixed namespaces.

@RavanH
Copy link
Author

RavanH commented Apr 6, 2024

Pruned translate_options_keys (some already detected), moved translation homepages to page sitemap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment