Polylang: Allow previews in multi-domain setup
Published: – Leave a comment
If you’re using Polylang in a multi-domain setup as I do in a project, there may be some problems regarding viewing post previews. To solve this, I tried various potential solutions and take you on the journey on how I to finally got rid of the problem – at least for pages.
The problem
The main problem in our setup is that we have a single (external) login mechanism per website – not per domain. And since domains don’t share cookies nowadays (which is a good thing), a login via one domain cannot be used for another domain. That means, if you have the domain example.com
for your English content and the domain example.de
for your German content, you cannot be logged in both domains with this single login mechanism.
That results in e.g. preview URLs not being viewable for all domains since a login is needed.
This is especially crucial if you want to setup a new language first (while it’s still disabled), which also means that you need to translate all pages and insert its new content. During this time you don’t want to make pages of this language public and so you need the preview functionality in order to verify that your pages look good after translation.
Besides that, we also have additional problems in our setup, since we use a domain mapping and the content of the website is being cached on the web server side in its page cache for 20 minutes for non-logged in uses. So even if it would be okay to have the new language available publicly in an unfinished state, it still would take up to 20 minutes for changes to show up on the actual page.
At first: multiple (potential) solutions
We thought about multiple solutions and we tried some of them, but not all.
Disable Polylang in the frontend for logged-in users
The first idea was to disable polylang in the frontend completely for logged-in users. While this would have made the preview URLs working for logged-in users, there was still a problem to login all users for all domains. At this point, we had a workaround for our customers with the single login mechanism, but it turned out to be unreliable since the customer had to manually adjust the URL after login, which was a bigger problem than we first thought. So we actually never tried this solution.
Change the URL modification type to folder names for logged-in users
This idea was very promising: instead of trying to workaround the multi-domain setup we just thought to change the URL modification type of Polylang to folder names for logged-in users. This would result in the languages being available on example.com/
as well as example.com/de/
.
The problem here is, especially for the customer, who wants to edit the pages by itself, that internal links or links to other languages manually have to be adjusted (e.g. to make sure all links to German pages are linked with example.de
, even though they are accessible via example.com/de/
for the customer). This would be a huge risk in having wrong URLs on the website, so it wouldn’t be very customer-friendly.
Disable Polylang for preview URLs
At this point, I thought we now had the final solution. It was not the most beautiful one, because disabling Polylang entirely for preview URLs would make content visible that is explicitly disabled for certain languages. Also the menu would always be the English one (or the one for the default set language), but at least the preview would work.
Or that’s what I first thought. After implementing and testing this solution, I discovered a problem: If pages of multiple languages have the same slug (the same path in their URL), it won’t work. Instead, the page of the default language would be displayed every time.
Disable the permalink structure for preview URLs
To workaround the slug problem, I thought I can just disable the permalink structure (which means, using the “Plain” method), which will only use the post ID in the URL instead of the slug.
This was working well after some testing, but since there is no filter or similar I needed to manually overwrite the actual permalink structure for every preview request. This was kind of hacky and I had to store the actual permalink structure elsewhere to restore it afterwards properly. That’s why I was not happy with this solution and searched for something else.
Finally: the solution
As I looked for how the preview works for non-published pages, I discovered that there are three URL parameters available: ?page_id=123&preview=true&lang=nn
For an already published page, the parameters were differently: ?preview_id=67565&preview_nonce=6c67bb8f51&preview=true
So as long as I could add the page_id
and the lang parameter to the preview URL, it should work. And luckily, it did!
Additionally, we also needed to make sure to use the currently used domain by the customer to show the preview so that we can make sure he’s logged in. So we just use the URL query of the actual preview URL, extend it and use the current home_url
, which is the domain the user currently uses – thanks to domain-mapping.
This is what the code looks like:
/**
* Add the 'lang' and 'page_id' URL parameters to the preview link.
*
* This allows to display the correct post for previews even pages with identical slug.
*
* @param string $preview_link Current preview link
* @param WP_Post $post Current post object
* @return string Updated preview link
*/
function add_preview_link_parameters( string $preview_link, \WP_Post $post ): string {
$option = \get_option( 'polylang' );
// the problem needs only to be solved if domains are used
if ( ! isset( $option['force_lang'] ) || $option['force_lang'] !== self::URL_MODIFICATION_DOMAIN ) {
return $preview_link;
}
// get the current preview URL query
$query = \wp_parse_url( $preview_link, \PHP_URL_QUERY );
// get the current home_url for domain mapping support and extend it with the preview URL query
$new_link = \home_url( '?' . $query );
// add lang and page ID query parameter to make sure to display this exact page in the desired language
$new_link = \add_query_arg(
[
'lang' => \pll_get_post_language( $post->ID, 'slug' ),
'page_id' => $post->ID,
],
$new_link
);
return $new_link;
}
\add_filter( 'preview_post_link', 'add_preview_link_parameters', 30, 2 );
Code language: PHP (php)
We cannot use the p
parameter to allow this to work on every post since then WordPress detects this preview as invalid for published posts and thus doesn’t display the preview but instead the current published content of the post.