How To make WPSiteSync work with WPML in WordPress
Category : bug fix , Tipps & Tricks , WordPress
In a customer project we have been asked how to get the WPSiteSync Plugin working together with WPML in a multisite / multilanguage scenario. Even the creators of WPSiteSync apologized, that their tool isn’t working correctly together with WPML and they have – at least currently – no plans to integrate that. Therefore so here’s an approach that is not thoroughly tested to work under all circumstances but at least tested good enough to work in a predefined scenario to solve the issues.
What is WPSiteSync?
The basic free version of WPSiteSync was created for a realistic setup in mid-scale to larger WordPress Projects where you would have a LIVE/PROD instance, a QA or TESTING instance and perhaps – for development purpose – a DEV instance. WPSiteSync is linking 2 instances together (here PROD and QA) so content editors could work on their content on QA and if done, checked, ready to go (or whatever the workflow might be…) push it to the Live site (PROD).
Would be also quite enough to call the scenario SOURCE <-> TARGET but that’s more academic, so let’s stick with a realistic QA <-> LIVE setup
How the developers describe the use of WPSiteSync themselves:
With WPSiteSync, the new workflow looks like this:
- Create Locally (Development Site)
- Build / Break / Fix Site
- Test Site Locally
- Deploy Site to LIVE host
- Create or Change Content on Your Local or Staging Site
- Push New Additions/Changes from Local or Staging to Live Site
Example use cases:
- You’ve created content (blog Post or Page) and need to move it to/from your Live site
- You administer the site but have contributors
- You run an eCommerce site and wish to update Pages without overwriting Purchase and Customer information
- You want to add or edit Products in your store and move these to the Live store (Premium Extensions Required)
- You’re using Gutenberg and want to easily move complex Page content from Staging to Live site
- You only need to synchronize specific content (not the complete database)
What is the default scope of WPSiteSync?
To put it that way: it is very useful for pushing content from one instance to another, e.g. from QA to LIVE. Only Content, not source files from the system. If you stick with that and perhaps have also added the premium plugins bundle which offers custom post types, forms and WooCommerce support, menus, authors and such, then it might be a good choice to handle the sync between an (internal) QA instance and the LIVE website.
How WPSiteSync behaves in a multilanguage setup?
Let’s imagine your company is internationally engaged or at least wants to offer its website content to a more international audience like English, French, Spanish, German. Then you might have translations. And if so, the technical solution for that might be implementing this by using the well-known WPML Plugin (The WordPress multilingual plugin). There are other translation plugins available also for free, but this scenario covers only WPML and is thus not suitable for other translation plugins, but might show a way how to solve such issues in a similar manner.
What is WPML?
WPML is widely know and perhaps the most complete and stable multilanguage solution for WordPress in a single plugin. For the ease of it, let’s stick with the features from the WPML developer’s site:
One WordPress Installation – Multiple Languages
WPML makes it easy to run a multilingual website with a single WordPress installation. Choose languages for your site and start translating content.
WPML comes with over 40 languages. You can also add your own language variants (like Canadian French or Mexican Spanish) using WPML’s languages editor.
Posts and pages are just the beginning. With WPML, you can translate custom post types, custom fields, widgets, menus, images, taxonomy, media – even texts in your site’s admin.
What happens with WPML translated content if push to the target with WPSiteSync?
By default translated content will be pushed to the default language of the target system. Let’s assume we have
- english (default)
- german
- french
in our multilanguage setup, DE content will get pushed to EN on the target system and thus needs at least manual fixing. So the extra-value of using WPSiteSync might be gone, since it might me nearly the same effort as if re-creating same content on LIVE by copy/paste from QA.
With a little tweak of only two files it is possible to make WPSiteSync working nice with WPML and not only pushing it to the correct language but even glueing translations together on the target system.
The described workaround might not be perfect and feel free to create an extra addon plugin that hooks into original WPSiteSync or such, but here is just to explain what changes to the original source code make WPSiteSync treat WPML multilanguage scenarios correctly.
Changes needed in WPSiteSync
(v 1.7 described here)
Changes to apicontroller.php
Open the file /classes/apicontroller.php and add the following
//**************************** //set correct language global $sitepress; //TODO: set langauge only if does not match source_post language $target_post_language = $sitepress->get_language_for_element($target_post_id); //TO DO: use $element_language___language_code instead of $current_language // SyncDebug::log(__METHOD__.'():'.__LINE__ . ' Check if languages match: SOURCE='.$current_language.' / TARGET='. $target_post_language); // if($current_language === $target_post_language) SyncDebug::log(__METHOD__.'():'.__LINE__ . ' *** MATCH ***'); SyncDebug::log(__METHOD__.'():'.__LINE__ . ' Check if languages match: SOURCE='.$element_language___language_code.' / TARGET='. $target_post_language); if($element_language___language_code=== $target_post_language) SyncDebug::log(__METHOD__.'():'.__LINE__ . ' *** MATCH ***'); else { SyncDebug::log(__METHOD__.'():'.__LINE__ . ' *** DO NOT MATCH ***'); //TODO: check if translation and set all needed values //important! has to check if > 1, since original always shows up, too if(count($element_translations) >1 ) SyncDebug::log(__METHOD__.'():'.__LINE__ . ' BEFORE SETTING TARGET LANG: CHECK IF HAS TRANSLATIONS '. var_export(count($element_translations), TRUE)); //get siblings from source and match to target siblings SyncDebug::log(__METHOD__.'():'.__LINE__ . ' element_type => '. $content_type); $target_post_type = 'post_post'; //maybe better to not check for each post type, but would not work for taxonomies and so on if($post_data['post_type'] === 'post' || 'page' || 'news' /*or other custom post types*/) $target_post_type = 'post_'.$post_data['post_type']; else $target_post_type = 'tax_'.$post_data['post_type']; SyncDebug::log(__METHOD__.'():'.__LINE__ . ' $target_post_type => '. $target_post_type); $set_language_args = array( 'element_id' => $target_post_id, 'element_type' => $target_post_type, 'trid' => $target_post_id, 'language_code' => $element_language___language_code, 'source_language_code' => $element_language___source_language_code ); $check_do_action= do_action( 'wpml_set_element_language_details', $set_language_args ); SyncDebug::log(__METHOD__.'():'.__LINE__ . ' $check_do_action '. var_export($check_do_action , TRUE)); //if not successful, might be a taxonomy } // this lets add-ons know that the Push operation is complete. Ex: CPT add-on can handle additional taxonomies to update SyncDebug::log(__METHOD__.'():'.__LINE__ . " calling action 'spectrom_sync_push_content'"); do_action('spectrom_sync_push_content', $target_post_id, $post_data, $response);
at around line 357 where marked
$post_data = $this->post_raw('post_data', array());
SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - post_data=' . var_export($post_data, TRUE));
>>>>>>>>>>>
$this->source_post_id = abs($post_data['ID']);
SyncDebug::log(__METHOD__.'():' . __LINE__ . ' syncing post data Source ID#'. $this->source_post_id . ' - "' . $post_data['post_title'] . '"');
Changes to apirequest.php
Insert the following code to /classes/apirequest.php
$log_model = new SyncLogModel();
$log_model->log($remote_args);
global $sitepress;
// update settings
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' abs($data[\'post_id\']) =' . abs($data['post_id']));
$default_language = $sitepress->get_default_language();
$current_language = $sitepress->get_current_language();
$element_trid=$sitepress->get_element_trid(abs($data['post_id']));
$translations = $sitepress->get_original_element_translation($element_trid, 'page');
$element_language = $sitepress->get_element_language_details(abs($data['post_id']));
$element_language___trid= $post_data['sitepress']['element_language']['trid'];
$element_translations = $sitepress->get_element_translations($element_trid);
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $$$translations =' . var_export($translations, TRUE));
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $$element_translations =' . var_export($element_translations, TRUE));
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $$$element_language___trid =' . $element_language___trid);
$test_translation=$sitepress->get_element_translations_object($data['post_type']);
$sitepress->language_attributes($output);
$the_languages=$sitepress->get_languages();
$active_langs = $sitepress->get_active_languages();
$active_langs = apply_filters( 'wpml_active_languages_access', $active_langs, array( 'action' => 'edit', 'post_type' => $this->post_type_label, 'post_id' => $this->post->ID ) );
foreach ( $active_langs as $lang ) {
$test_lang1=esc_html( $lang['display_name'] );
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $test_lang1 =' . $test_lang1);
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' abs($data[\'post_id\']) =' . abs($data['post_id']));
}
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $test_translation =' . var_export($test_translation, TRUE));
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' $test_translation =' . var_export($the_languages, TRUE));
SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' ########## END OF LANGUAGES');
if (!isset($remote_args['sitepress'])) {
$remote_args['body']['post_data']['sitepress']['language'] = "test";
$remote_args['body']['post_data']['sitepress']['default_language'] = $default_language;
$remote_args['body']['post_data']['sitepress']['current_language'] = $current_language;
$remote_args['body']['post_data']['sitepress']['element_language'] = $element_language;
$remote_args['body']['post_data']['sitepress']['translations'] = $translations;
$remote_args['body']['post_data']['sitepress']['element_translations'] = $element_translations;
$remote_args['body']['post_data']['sitepress']['translations'] = "yes/no";
$remote_args['body']['post_data']['sitepress']['transl_ids'] = "1,2,3";
}
//########################
SyncDebug::log(__METHOD__ . '########## sitepress ##########');
$log_model = new SyncLogModel();
$log_model->log($remote_args);
at around line 190 before
// send data where it's going
and then
if (isset($post_data['sitepress']))
$data['sitepress'] = $post_data['sitepress'];
somewhere at line 722 where all the post_data gets set
Since this still might be not perfect, test it yourself first in a localhost environment with 2 instances Source & Target. You need to have WPML installed for that and at least 1 additional language to the default.
There might still be superfluous or debug code but it should point a way how to use it and might also give hints for adding other translation plugins than WPML