Skip to content

Instantly share code, notes, and snippets.

@asdfMaciej
Last active October 29, 2023 11:28
Show Gist options
  • Save asdfMaciej/da3696f05c1af68d9903974be01f1e4a to your computer and use it in GitHub Desktop.
Save asdfMaciej/da3696f05c1af68d9903974be01f1e4a to your computer and use it in GitHub Desktop.
Polylang - rewrite permalinks to directories and domains
<?php
// Created by Maciej Kaszkowiak (maciej@kaszkowiak.org)
// Implemented according to https://wordpress.org/support/topic/multiple-languages-in-the-same-domain/
// Modify PLL_Links_Sterowniki.hosts and PLL_Links_Sterowniki.protocols in order to set domains
// The remaining languages will redirect to directories, such as /en/ or /it/
/**
* Links model for use when the language code is added in url as a directory
* for example mysite.com/en/something
* implements the "links_model interface"
*
* @since 1.2
*/
if (!function_exists('str_contains')) {
function str_contains($haystack, $needle) {
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
}
class PLL_Links_Sterowniki extends PLL_Links_Permalinks {
/**
* Relative path to the home url.
*
* @var string
*/
protected $home_relative;
protected $hosts = [
'de' => 'controller-reparatur.de'
];
protected $protocols = [
'de' => 'https'
];
/**
* Constructor.
*
* @since 1.2
*
* @param PLL_Model $model PLL_Model instance.
*/
public function __construct( &$model ) {
parent::__construct( $model );
$this->home_relative = home_url( '/', 'relative' );
if ( did_action( 'pll_init' ) ) {
$this->init();
} else {
add_action( 'pll_init', array( $this, 'init' ) );
}
}
/**
* Called only at first object creation to avoid duplicating filters when switching blog
*
* @since 1.6
*
* @return void
*/
public function init() {
if ( did_action( 'setup_theme' ) ) {
$this->add_permastruct();
} else {
add_action( 'setup_theme', array( $this, 'add_permastruct' ), 2 );
}
// Make sure to prepare rewrite rules when flushing
add_action( 'pre_option_rewrite_rules', array( $this, 'prepare_rewrite_rules' ) );
}
/**
* Adds the language code in a url.
* links_model interface.
*
* @since 1.2
*
* @param string $url The url to modify.
* @param PLL_Language $lang The language object.
* @return string Modified url.
*/
public function add_language_to_link( $url, $lang ) {
if ( ! empty( $lang ) && ! empty( $this->hosts[ $lang->slug ] ) ) {
$url = preg_replace( '#://(' . wp_parse_url( $this->home, PHP_URL_HOST ) . ')($|/.*)#', '://' . $this->hosts[ $lang->slug ] . '$2', $url );
return $url;
}
if ( ! empty( $lang ) ) {
$base = $this->options['rewrite'] ? '' : 'language/';
$slug = $this->options['default_lang'] == $lang->slug && $this->options['hide_default'] ? '' : $base . $lang->slug . '/';
$root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
if ( false === strpos( $url, $new = $root . $slug ) ) {
$pattern = preg_quote( $root, '#' );
$pattern = '#' . $pattern . '#';
return preg_replace( $pattern, $new, $url, 1 ); // Only once
}
}
return $url;
}
/**
* Returns the url without language code
* links_model interface
*
* @since 1.2
*
* @param string $url url to modify
* @return string modified url
*/
public function remove_language_from_link( $url ) {
$isDomainUrl = false;
foreach ($this->hosts as $lang => $domain) {
if (str_contains($url, $domain)) {
$isDomainUrl = $lang;
break;
}
}
if ($isDomainUrl) {
$url = preg_replace( '#://(' . implode( '|', $this->hosts ) . ')($|/.*)#', '://' . wp_parse_url( $this->home, PHP_URL_HOST ) . '$2', $url );
return $url;
}
$languages = array();
foreach ( $this->model->get_languages_list() as $language ) {
if ( ! $this->options['hide_default'] || $this->options['default_lang'] != $language->slug ) {
$languages[] = $language->slug;
}
}
if ( ! empty( $languages ) ) {
$root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root );
$pattern = preg_quote( $root, '#' );
$pattern = '#' . $pattern . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')(/|$)#';
$url = preg_replace( $pattern, $root, $url );
}
return $url;
}
/**
* Returns the language based on language code in url
* links_model interface
*
* @since 1.2
* @since 2.0 add $url argument
*
* @param string $url optional, defaults to current url
* @return string language slug
*/
public function get_language_from_url( $url = '' ) {
if ( empty( $url ) ) {
$url = pll_get_requested_url();
}
$isDomainUrl = false;
foreach ($this->hosts as $lang => $domain) {
if (str_contains($url, $domain)) {
$isDomainUrl = $lang;
break;
}
}
if ($isDomainUrl) {
return $isDomainUrl;
}
$path = wp_parse_url( $url, PHP_URL_PATH );
$root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root;
$pattern = wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH );
$pattern = preg_quote( $pattern, '#' );
$pattern = '#^' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(/|$)#';
return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language
}
/**
* Returns the home url in a given language.
* links_model interface.
*
* @since 1.3.1
*
* @param PLL_Language $lang PLL_Language object.
* @return string
*/
public function home_url( $lang ) {
if (is_string($lang)) {
$langObj = new stdClass();
$langObj->slug = $lang;
$lang = $langObj;
}
if (array_key_exists($lang->slug, $this->hosts)) {
$value = $this->protocols[$lang->slug] . '://' . trailingslashit($this->hosts[$lang->slug]);
return $value;
}
$base = $this->options['rewrite'] ? '' : 'language/';
$slug = $this->options['default_lang'] == $lang->slug && $this->options['hide_default'] ? '' : '/' . $this->root . $base . $lang->slug;
return trailingslashit( $this->home . $slug );
}
/**
* Optionally removes 'language' in permalinks so that we get http://www.myblog/en/ instead of http://www.myblog/language/en/
*
* @since 1.2
*
* @return void
*/
public function add_permastruct() {
// Language information always in front of the uri ( 'with_front' => false )
// The 3rd parameter structure has been modified in WP 3.4
// Leads to error 404 for pages when there is no language created yet
if ( $this->model->get_languages_list() ) {
add_permastruct( 'language', $this->options['rewrite'] ? '%language%' : 'language/%language%', array( 'with_front' => false ) );
}
}
/**
* Prepares the rewrite rules filters.
*
* @since 0.8.1
*
* @param mixed $pre Not used as the filter is used as an action.
* @return mixed
*/
public function prepare_rewrite_rules( $pre ) {
// Don't modify the rules if there is no languages created yet
// Make sure to add filter only once and if all custom post types and taxonomies have been registered
if ( $this->model->get_languages_list() && did_action( 'wp_loaded' ) && ! has_filter( 'language_rewrite_rules', '__return_empty_array' ) ) {
// Suppress the rules created by WordPress for our taxonomy
add_filter( 'language_rewrite_rules', '__return_empty_array' );
foreach ( $this->get_rewrite_rules_filters() as $type ) {
add_filter( $type . '_rewrite_rules', array( $this, 'rewrite_rules' ) );
}
add_filter( 'rewrite_rules_array', array( $this, 'rewrite_rules' ) ); // needed for post type archives
}
return $pre;
}
/**
* The rewrite rules !
* Always make sure that the default language is at the end in case the language information is hidden for default language.
* Thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct.
*
* @since 0.8.1
*
* @param string[] $rules Rewrite rules.
* @return string[] Modified rewrite rules.
*/
public function rewrite_rules( $rules ) {
$filter = str_replace( '_rewrite_rules', '', current_filter() );
global $wp_rewrite;
$newrules = array();
$languages = $this->model->get_languages_list( array( 'fields' => 'slug' ) );
if ( $this->options['hide_default'] ) {
$languages = array_diff( $languages, array( $this->options['default_lang'] ) );
}
if ( ! empty( $languages ) ) {
$slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')/';
}
// For custom post type archives
$cpts = array_intersect( $this->model->get_translated_post_types(), get_post_types( array( '_builtin' => false ) ) );
$cpts = $cpts ? '#post_type=(' . implode( '|', $cpts ) . ')#' : '';
foreach ( $rules as $key => $rule ) {
// Special case for translated post types and taxonomies to allow canonical redirection
if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) {
/**
* Filters the rewrite rules to modify
*
* @since 1.9.1
*
* @param bool $modify whether to modify or not the rule, defaults to true
* @param array $rule original rewrite rule
* @param string $filter current set of rules being modified
* @param string|bool $archive custom post post type archive name or false if it is not a cpt archive
*/
if ( isset( $slug ) && apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) {
$newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ),
$rule
); // Should be enough!
}
$newrules[ $key ] = $rule;
}
// Rewrite rules filtered by language
elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) {
/** This filter is documented in include/links-directory.php */
if ( apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, empty( $matches[1] ) ? false : $matches[1] ) ) {
if ( isset( $slug ) ) {
$newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace(
array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ),
array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ),
$rule
); // Should be enough!
}
if ( $this->options['hide_default'] ) {
$newrules[ $key ] = str_replace( '?', '?lang=' . $this->options['default_lang'] . '&', $rule );
}
} else {
$newrules[ $key ] = $rule;
}
}
// Unmodified rules
else {
$newrules[ $key ] = $rule;
}
}
// The home rewrite rule
if ( 'root' == $filter && isset( $slug ) ) {
$newrules[ $slug . '?$' ] = $wp_rewrite->index . '?lang=$matches[1]';
}
return $newrules;
}
}
function custom_pll_links_model_filter($class) {
return PLL_Links_Sterowniki::class;
}
define("PLL_CACHE_LANGUAGES", false);
add_filter('pll_links_model', 'custom_pll_links_model_filter');
@asdfMaciej
Copy link
Author

This snippet for Polylang redirects some languages to specified domains, and the remaining languages to directories on the main domain. I've created it by combining PLL_Links_Directory and PLL_Links_Domain classes with some extra logic.

In the above example, links in an English website example.com will look as follow:

'de' => 'controller-reparatur.de'
'en' => 'domain.com'
'it' => 'domain.com/it/'

Modify $hosts and $protocols in order to customize the domains.

This snippet hasn't been thoroughly tested, but it works for my small website. At least, it's a good starting point! :)

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