Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards. - Added getallheaders function to retrieve HTTP headers in a compatible manner. - Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts. - Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
if(!class_exists('ATFPP_Bulk_Translation')):
|
||||
class ATFPP_Bulk_Translation
|
||||
{
|
||||
private static $instance;
|
||||
|
||||
public static function get_instance()
|
||||
{
|
||||
if(!isset(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
add_action('current_screen', array($this, 'bulk_translate_btn'));
|
||||
}
|
||||
|
||||
public function bulk_translate_btn($screen)
|
||||
{
|
||||
global $polylang;
|
||||
|
||||
if(!$polylang || !property_exists($polylang, 'model')){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!class_exists('ATFPP_Helper') || !ATFPP_Helper::bulk_translation_render($screen)){
|
||||
return;
|
||||
}
|
||||
|
||||
$post_status=isset($_GET['post_status']) ? sanitize_text_field(wp_unslash($_GET['post_status'])) : '';
|
||||
|
||||
if('trash' === $post_status){
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( "views_{$screen->id}", array($this, 'atfpp_bulk_translate_button') );
|
||||
|
||||
add_action('admin_footer', array($this, 'bulk_translate_container'));
|
||||
}
|
||||
|
||||
public function atfpp_bulk_translate_button($views)
|
||||
{
|
||||
echo "<button class='button atfpp-bulk-translate-btn' style='display:none;'>Bulk Translate</button>";
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
public function bulk_translate_container()
|
||||
{
|
||||
echo "<div id='atfpp-bulk-translate-wrapper'></div>";
|
||||
}
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AutoPoly - AI Translation For Polylang (Pro)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model for synchronizing posts
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
class ATFP_Posts_Clone {
|
||||
/**
|
||||
* Stores the plugin options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @var PLL_Model
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* @var PLL_Sync
|
||||
*/
|
||||
public $sync;
|
||||
|
||||
/**
|
||||
* @var PLL_Sync_Content
|
||||
*/
|
||||
public $sync_content;
|
||||
|
||||
/**
|
||||
* Stores temporary a synchronization information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $temp_synchronized;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param object $polylang Polylang object.
|
||||
*/
|
||||
public function __construct( &$polylang ) {
|
||||
$this->options = &$polylang->options;
|
||||
$this->model = &$polylang->model;
|
||||
$this->sync = &$polylang->sync;
|
||||
$this->sync_content = new ATFP_Sync_Content($polylang);
|
||||
|
||||
add_filter( 'pll_copy_taxonomies', array( $this, 'copy_taxonomies' ), 5, 4 );
|
||||
add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 5, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all taxonomies.
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param string[] $taxonomies List of taxonomy names.
|
||||
* @param bool $sync True for a synchronization, false for a simple copy.
|
||||
* @param int $from Source post id.
|
||||
* @param int $to Target post id.
|
||||
* @return string[]
|
||||
*/
|
||||
public function copy_taxonomies( $taxonomies, $sync, $from, $to ) {
|
||||
if ( ! empty( $from ) && ! empty( $to ) && $this->are_synchronized( $from, $to ) ) {
|
||||
$taxonomies = array_diff( get_post_taxonomies( $from ), get_taxonomies( array( '_pll' => true ) ) );
|
||||
}
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all custom fields.
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param string[] $keys List of custom fields names.
|
||||
* @param bool $sync True if it is synchronization, false if it is a copy.
|
||||
* @param int $from Id of the post from which we copy the information.
|
||||
* @param int $to Id of the post to which we paste the information.
|
||||
* @return string[]
|
||||
*/
|
||||
public function copy_post_metas( $keys, $sync, $from, $to ) {
|
||||
if ( ! empty( $from ) && ! empty( $to ) && $this->are_synchronized( $from, $to ) ) {
|
||||
$from_keys = array_keys( get_post_custom( $from ) ); // *All* custom fields.
|
||||
$to_keys = array_keys( get_post_custom( $to ) ); // Adding custom fields of the destination allow to synchronize deleted custom fields.
|
||||
$keys = array_merge( $from_keys, $to_keys );
|
||||
$keys = array_unique( $keys );
|
||||
$keys = array_diff( $keys, array( '_edit_last', '_edit_lock' ) );
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates the post to one language and optionally saves the synchronization group
|
||||
*
|
||||
* @since 2.2
|
||||
*
|
||||
* @param int $post_id Post id of the source post.
|
||||
* @param string $source_language Source language slug.
|
||||
* @param string $target_language Target language slug.
|
||||
* @param bool $save_group True to update the synchronization group, false otherwise.
|
||||
* @return int Id of the target post, 0 on failure.
|
||||
*/
|
||||
public function copy_post( $post_id, $source_language, $target_language, $save_group = true, $post_data = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$tr_id = $this->model->post->get( $post_id, $this->model->get_language( $target_language ) );
|
||||
$tr_post = get_post( $post_id );
|
||||
$languages = array_keys( $this->get( $post_id ) );
|
||||
|
||||
if ( ! $tr_post instanceof WP_Post ) {
|
||||
// Something went wrong!
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach($tr_post as $key => $value){
|
||||
if(isset($post_data[$key]) && $key !== 'post_meta_fields'){
|
||||
$tr_post->$key = $post_data[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($tr_post->post_content)){
|
||||
$tr_post->post_content=ATFPP_Helper::replace_links_with_translations($tr_post->post_content, $target_language, $source_language);
|
||||
}
|
||||
|
||||
// If it does not exist, create it.
|
||||
if ( ! $tr_id ) {
|
||||
$tr_post->ID = 0;
|
||||
$tr_post->post_status = get_option('atfp_bulk_post_status', 'draft');
|
||||
|
||||
$tr_id = wp_insert_post( wp_slash( $tr_post->to_array() ) );
|
||||
$this->model->post->set_language( $tr_id, $target_language ); // Necessary to do it now to share slug.
|
||||
|
||||
$translations = $this->model->post->get_translations( $post_id );
|
||||
$translations[ $target_language ] = $tr_id;
|
||||
$this->model->post->save_translations( $post_id, $translations ); // Saves translations in case we created a post.
|
||||
|
||||
$languages[] = $target_language;
|
||||
|
||||
// Temporarily sync group, even if false === $save_group as we need synchronized posts to copy *all* taxonomies and post metas.
|
||||
$this->temp_synchronized[ $post_id ][ $tr_id ] = true;
|
||||
|
||||
// Maybe duplicates the featured image.
|
||||
if ( $this->options['media_support'] ) {
|
||||
add_filter( 'pll_translate_post_meta', array( $this->sync_content, 'duplicate_thumbnail' ), 10, 3 );
|
||||
}
|
||||
|
||||
add_filter( 'pll_maybe_translate_term', array( $this->sync_content, 'duplicate_term' ), 10, 3 );
|
||||
|
||||
$this->sync->taxonomies->copy( $post_id, $tr_id, $target_language );
|
||||
$this->sync->post_metas->copy( $post_id, $tr_id, $target_language );
|
||||
|
||||
$_POST['post_tr_lang'][ $target_language ] = $tr_id; // Hack to avoid creating multiple posts if the original post is saved several times (ex WooCommerce 3.0+).
|
||||
|
||||
/**
|
||||
* Fires after a synchronized post has been created
|
||||
*
|
||||
* @since 2.3.11
|
||||
*
|
||||
* @param int $post_id Id of the source post.
|
||||
* @param int $tr_id Id of the newly created post.
|
||||
* @param string $lang Language of the newly created post.
|
||||
*/
|
||||
do_action( 'pll_created_sync_post', $post_id, $tr_id, $target_language );
|
||||
|
||||
$post=get_post($post_id);
|
||||
do_action( 'pll_save_post', $post_id, $post, $translations ); // Fire again as we just updated $translations.
|
||||
|
||||
unset( $this->temp_synchronized[ $post_id ][ $tr_id ] );
|
||||
}
|
||||
|
||||
if ( $save_group ) {
|
||||
$this->save_group( $post_id, $languages );
|
||||
}
|
||||
|
||||
$tr_post->ID = $tr_id;
|
||||
$post=get_post($post_id);
|
||||
|
||||
$tr_post->post_parent = (int) $this->model->post->get( $post->post_parent, $target_language ); // Translates post parent.
|
||||
|
||||
$post = clone $tr_post;
|
||||
$post->ID=$post_id;
|
||||
|
||||
$tr_post = $this->sync_content->copy_content( $post, $tr_post, $target_language );
|
||||
|
||||
// The columns to copy in DB.
|
||||
$columns = array(
|
||||
'post_author',
|
||||
'post_date',
|
||||
'post_date_gmt',
|
||||
'post_content',
|
||||
'post_title',
|
||||
'post_excerpt',
|
||||
'comment_status',
|
||||
'ping_status',
|
||||
'post_name',
|
||||
'post_modified',
|
||||
'post_modified_gmt',
|
||||
'post_parent',
|
||||
'menu_order',
|
||||
'post_mime_type',
|
||||
);
|
||||
|
||||
$columns[] = 'post_status';
|
||||
|
||||
is_sticky( $post_id ) ? stick_post( $tr_id ) : unstick_post( $tr_id );
|
||||
|
||||
/**
|
||||
* Filters the post fields to synchronize when synchronizing posts
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param array $fields WP_Post fields to synchronize.
|
||||
* @param int $post_id Post id of the source post.
|
||||
* @param string $lang Target language slug.
|
||||
* @param bool $save_group True to update the synchronization group, false otherwise.
|
||||
*/
|
||||
$columns = apply_filters( 'pll_sync_post_fields', array_combine( $columns, $columns ), $post_id, $target_language, $save_group );
|
||||
|
||||
$tr_post = array_intersect_key( (array) $tr_post, $columns );
|
||||
|
||||
$wpdb->update( $wpdb->posts, $tr_post, array( 'ID' => (int) $tr_id ) ); // Don't use wp_update_post to avoid conflict (reverse sync).
|
||||
clean_post_cache( $tr_id );
|
||||
|
||||
$post_meta_sync=true;
|
||||
|
||||
if (!isset(PLL()->options['sync']) || (isset(PLL()->options['sync']) && !in_array('post_meta', PLL()->options['sync']))) {
|
||||
$post_meta_sync = false;
|
||||
}
|
||||
|
||||
if(!$post_meta_sync && isset($post_data['post_meta_fields']) && count($post_data['post_meta_fields']) > 0){
|
||||
$this->update_post_custom_fields($post_data['post_meta_fields'], $tr_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after a post has been synchronized.
|
||||
*
|
||||
* @since 2.6.3
|
||||
*
|
||||
* @param int $post_id Id of the source post.
|
||||
* @param int $tr_id Id of the target post.
|
||||
* @param string $lang Language of the target post.
|
||||
* @param string $strategy `copy`.
|
||||
*/
|
||||
do_action( 'pll_post_synchronized', $post_id, $tr_id, $target_language, 'copy' );
|
||||
|
||||
// Update Elementor Translations
|
||||
$this->update_elementor_data($tr_id, $post_data, $post_id);
|
||||
|
||||
return $tr_id;
|
||||
}
|
||||
|
||||
private function update_post_custom_fields($fields, $post_id){
|
||||
$post_meta_sync = true;
|
||||
|
||||
if (!isset(PLL()->options['sync']) || (isset(PLL()->options['sync']) && !in_array('post_meta', PLL()->options['sync']))) {
|
||||
$post_meta_sync = false;
|
||||
}
|
||||
|
||||
if($post_meta_sync){
|
||||
return;
|
||||
}
|
||||
|
||||
$allowed_meta_fields=ATFPP_Helper::get_allowed_custom_fields();
|
||||
|
||||
if($fields && is_array($fields) && count($fields) > 0){
|
||||
$valid_meta_fields=array_intersect(array_keys($fields), array_keys($allowed_meta_fields));
|
||||
if(count($valid_meta_fields) > 0){
|
||||
foreach($valid_meta_fields as $key){
|
||||
if(isset($allowed_meta_fields[$key]) && $allowed_meta_fields[$key]['status']){
|
||||
$value=is_array($fields[$key]) ? $this->sanitize_array_value($fields[$key], array()) : sanitize_text_field($fields[$key]);
|
||||
|
||||
update_post_meta(absint($post_id), sanitize_text_field($key), $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sanitize_array_value($value, $arr){
|
||||
foreach($value as $key => $item){
|
||||
$arr[sanitize_text_field($key)]=is_array($item) ? $this->sanitize_array_value($item, array()) : sanitize_text_field($item);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Elementor data
|
||||
*
|
||||
* @param int $tr_id The ID of the translated post.
|
||||
* @param string $elementor_data The Elementor data to update.
|
||||
* @return void
|
||||
*/
|
||||
private function update_elementor_data($tr_id, $post_data, $parent_post_id = 0){
|
||||
$current_post_elementor_data = get_post_meta($tr_id, '_elementor_data', true);
|
||||
|
||||
if(!isset($post_data['meta_fields']['_elementor_data'])){
|
||||
return;
|
||||
}
|
||||
|
||||
$elementor_data=$post_data['meta_fields']['_elementor_data'];
|
||||
|
||||
// Check if the current post has Elementor data
|
||||
if('' !== $current_post_elementor_data && $elementor_data && '' !== $elementor_data){
|
||||
if(class_exists('Elementor\Plugin')){
|
||||
$plugin=\Elementor\Plugin::$instance;
|
||||
$document=$plugin->documents->get($tr_id);
|
||||
|
||||
$document->save( [
|
||||
'elements' => json_decode($elementor_data, true),
|
||||
] );
|
||||
|
||||
$plugin->files_manager->clear_cache();
|
||||
}else{
|
||||
|
||||
if($parent_post_id > 0){
|
||||
$elementor_data=\Elementor\Plugin::$instance->documents->get($parent_post_id)->get_elements_data();
|
||||
$elementor_data=wp_json_encode($elementor_data);
|
||||
$elementor_data=preg_replace('#(?<!\\\\)/#', '\\/', $elementor_data);
|
||||
update_post_meta($tr_id, '_elementor_data', $elementor_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the synchronization group
|
||||
* This is stored as an array beside the translations in the post_translations term description
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param int $post_id ID of the post currently being saved.
|
||||
* @param array $sync_post Array of languages to sync with this post.
|
||||
* @return void
|
||||
*/
|
||||
public function save_group( $post_id, $sync_post ) {
|
||||
$term = $this->model->post->get_object_term( $post_id, 'post_translations' );
|
||||
|
||||
if ( empty( $term ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$d = maybe_unserialize( $term->description );
|
||||
$lang = $this->model->post->get_language( $post_id );
|
||||
|
||||
if ( ! is_array( $d ) || empty( $lang ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lang = $lang->slug;
|
||||
|
||||
if ( empty( $sync_post ) ) {
|
||||
if ( isset( $d['sync'][ $lang ] ) ) {
|
||||
$d['sync'] = array_diff( $d['sync'], array( $d['sync'][ $lang ] ) );
|
||||
}
|
||||
} else {
|
||||
$sync_post[] = $lang;
|
||||
$d['sync'] = empty( $d['sync'] ) ? array_fill_keys( $sync_post, $lang ) : array_merge( array_diff( $d['sync'], array( $lang ) ), array_fill_keys( $sync_post, $lang ) );
|
||||
}
|
||||
|
||||
wp_update_term( (int) $term->term_id, 'post_translations', array( 'description' => maybe_serialize( $d ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all posts synchronized with a given post
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param int $post_id The id of the post.
|
||||
* @return array An associative array of arrays with language code as key and post id as value.
|
||||
*/
|
||||
public function get( $post_id ) {
|
||||
$term = $this->model->post->get_object_term( $post_id, 'post_translations' );
|
||||
|
||||
if ( ! empty( $term ) ) {
|
||||
$lang = $this->model->post->get_language( $post_id );
|
||||
$d = maybe_unserialize( $term->description );
|
||||
|
||||
if ( ! is_array( $d ) || empty( $lang ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( ! empty( $d['sync'][ $lang->slug ] ) ) {
|
||||
$keys = array_keys( $d['sync'], $d['sync'][ $lang->slug ] );
|
||||
return array_intersect_key( $d, array_flip( $keys ) );
|
||||
}
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether two posts are synchronized
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param int $post_id The id of a first post to compare.
|
||||
* @param int $other_id The id of the other post to compare.
|
||||
* @return bool
|
||||
*/
|
||||
public function are_synchronized( $post_id, $other_id ) {
|
||||
return isset( $this->temp_synchronized[ $post_id ][ $other_id ] ) || in_array( $other_id, $this->get( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user can synchronize a post in other language
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param int $post_id Post to synchronize.
|
||||
* @param string $lang Language code.
|
||||
* @return bool
|
||||
*/
|
||||
public function current_user_can_synchronize( $post_id, $lang ) {
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tr_id = $this->model->post->get( $post_id, $this->model->get_language( $lang ) );
|
||||
|
||||
// If we don't have a translation yet, check if we have the right to create a new one?
|
||||
if ( empty( $tr_id ) ) {
|
||||
$post_type = get_post_type( $post_id );
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
return current_user_can( $post_type_object->cap->create_posts );
|
||||
}
|
||||
|
||||
// Do we have the right to edit this translation?
|
||||
if ( ! current_user_can( 'edit_post', $tr_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is this translation synchronized with a post that we can't edit?
|
||||
$ids = $this->get( $tr_id );
|
||||
|
||||
foreach ( $ids as $id ) {
|
||||
if ( ! current_user_can( 'edit_post', $id ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AutoPoly - AI Translation For Polylang (Pro)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smart copy of post content
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
class ATFP_Sync_Content {
|
||||
/**
|
||||
* Stores the plugin options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var PLL_Model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Instance of a child class of PLL_Links_Model.
|
||||
*
|
||||
* @var PLL_Links_Model
|
||||
*/
|
||||
protected $links_model;
|
||||
|
||||
/**
|
||||
* @var PLL_CRUD_Posts
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* The post object to fill with translated data.
|
||||
*
|
||||
* @var WP_Post
|
||||
*/
|
||||
protected $target_post;
|
||||
|
||||
/**
|
||||
* Language of the target post.
|
||||
*
|
||||
* @var PLL_Language
|
||||
*/
|
||||
protected $target_language;
|
||||
|
||||
/**
|
||||
* Language of the source post.
|
||||
*
|
||||
* @var PLL_Language
|
||||
*/
|
||||
protected $from_language;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param PLL_Frontend|PLL_Admin|PLL_Settings|PLL_REST_Request $polylang Polylang object.
|
||||
*/
|
||||
public function __construct( &$polylang ) {
|
||||
$this->options = &$polylang->options;
|
||||
$this->model = &$polylang->model;
|
||||
$this->posts = &$polylang->posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the content from one post to the other
|
||||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param WP_Post $from_post The post to copy from.
|
||||
* @param WP_Post $target_post The post to copy to.
|
||||
* @param PLL_Language|string $target_language The language of the post to copy to.
|
||||
* @return WP_Post|void
|
||||
*/
|
||||
public function copy_content( $from_post, $target_post, $target_language ) {
|
||||
$from_language = $this->model->post->get_language( $from_post->ID );
|
||||
$target_language = $this->model->get_language( $target_language );
|
||||
|
||||
if ( ! $from_language || ! $target_language ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target_post->post_title = $from_post->post_title;
|
||||
$target_post->post_name = wp_unique_post_slug(
|
||||
$from_post->post_name,
|
||||
$target_post->ID,
|
||||
$target_post->post_status,
|
||||
$target_post->post_type,
|
||||
$target_post->post_parent
|
||||
);
|
||||
$target_post->post_excerpt = $this->translate_content(
|
||||
$from_post->post_excerpt,
|
||||
$target_post,
|
||||
$from_language,
|
||||
$target_language
|
||||
);
|
||||
$target_post->post_content = $this->translate_content(
|
||||
$from_post->post_content,
|
||||
$target_post,
|
||||
$from_language,
|
||||
$target_language
|
||||
);
|
||||
|
||||
return $target_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate shortcodes and <img> attributes in a given text
|
||||
*
|
||||
* @since 1.9
|
||||
* @since 3.3 Requires $target_post, $from_language and $target_language parameters.
|
||||
* @global array $shortcode_tags
|
||||
*
|
||||
* @param string $content Text to translate.
|
||||
* @param WP_Post $target_post The post object to populate with translated content.
|
||||
* @param PLL_Language $from_language The source language .
|
||||
* @param PLL_Language $target_language The language to translate to.
|
||||
* @return string Translated text
|
||||
*/
|
||||
public function translate_content( $content, $target_post, PLL_Language $from_language, PLL_Language $target_language ) {
|
||||
global $shortcode_tags;
|
||||
|
||||
$this->target_post = $target_post;
|
||||
$this->from_language = $from_language;
|
||||
$this->target_language = $target_language;
|
||||
|
||||
// Hack shortcodes.
|
||||
$backup = $shortcode_tags;
|
||||
$shortcode_tags = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
|
||||
// Add our own shorcode actions.
|
||||
if ( $this->options['media_support'] ) {
|
||||
add_shortcode( 'gallery', array( $this, 'ids_list_shortcode' ) );
|
||||
add_shortcode( 'playlist', array( $this, 'ids_list_shortcode' ) );
|
||||
add_shortcode( 'caption', array( $this, 'caption_shortcode' ) );
|
||||
add_shortcode( 'wp_caption', array( $this, 'caption_shortcode' ) );
|
||||
}
|
||||
|
||||
if ( has_blocks( $content ) ) {
|
||||
$blocks = parse_blocks( $content );
|
||||
$blocks = $this->translate_blocks( $blocks );
|
||||
$content = serialize_blocks( $blocks );
|
||||
} else {
|
||||
$content = do_shortcode( $content ); // Translate shortcodes.
|
||||
$content = $this->translate_html( $content );
|
||||
}
|
||||
|
||||
// Get the shorcodes back.
|
||||
$shortcode_tags = $backup; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates the feature image if the translation does not exist yet.
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param int $id Thumbnail ID.
|
||||
* @param string $key Meta key.
|
||||
* @param string $lang Language code.
|
||||
* @return int
|
||||
*/
|
||||
public function duplicate_thumbnail( $id, $key, $lang ) {
|
||||
if ( '_thumbnail_id' === $key && ! $tr_id = $this->model->post->get( $id, $lang ) ) {
|
||||
$tr_id = $this->model->post->create_media_translation( $id, $lang );
|
||||
}
|
||||
return empty( $tr_id ) ? $id : $tr_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a term if the translation does not exist yet.
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param int $tr_term_id Translated term id.
|
||||
* @param int $term_id Source term id.
|
||||
* @param string $lang Language slug.
|
||||
* @return int The translated term id. O on failure.
|
||||
*/
|
||||
public function duplicate_term( $tr_term_id, $term_id, $lang ) {
|
||||
if ( empty( $tr_term_id ) ) {
|
||||
$term = get_term( $term_id );
|
||||
|
||||
if ( $term instanceof WP_Term ) {
|
||||
$language = $this->model->term->get_language( $term->term_id );
|
||||
|
||||
if ( $language && $language->slug !== $lang ) { // Create a new term translation only if the source term has a language.
|
||||
$tr_parent = empty( $term->parent ) ? 0 : (int) $this->model->term->get_translation( $term->parent, $lang );
|
||||
|
||||
// Duplicate the parent if the parent translation doesn't exist yet.
|
||||
if ( empty( $tr_parent ) && ! empty( $term->parent ) ) {
|
||||
$tr_parent = $this->duplicate_term( 0, $term->parent, $lang );
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'description' => wp_slash( $term->description ),
|
||||
'parent' => $tr_parent,
|
||||
);
|
||||
|
||||
if ( $this->options['force_lang'] ) {
|
||||
// Share slugs
|
||||
$args['slug'] = $term->slug . '___' . $lang;
|
||||
} else {
|
||||
// Language set from the content: assign a different slug
|
||||
// otherwise we would change the current term language instead of creating a new term
|
||||
$args['slug'] = sanitize_title( $term->name ) . '-' . $lang;
|
||||
}
|
||||
|
||||
$t = wp_insert_term( wp_slash( $term->name ), $term->taxonomy, $args );
|
||||
|
||||
$tr_term_id = 0;
|
||||
|
||||
if ( is_array( $t ) ) {
|
||||
$tr_term_id = $t['term_id'];
|
||||
$this->model->term->set_language( $tr_term_id, $lang );
|
||||
$translations = $this->model->term->get_translations( $term->term_id );
|
||||
$translations[ $lang ] = $tr_term_id;
|
||||
$this->model->term->save_translations( $term->term_id, $translations );
|
||||
|
||||
/**
|
||||
* Fires after a term translation is automatically created when duplicating a post.
|
||||
*
|
||||
* @since 2.3.8
|
||||
*
|
||||
* @param int $from Term ID of the source term.
|
||||
* @param int $to Term ID of the new term translation.
|
||||
* @param string $lang Language code of the new translation.
|
||||
*/
|
||||
do_action( 'pll_duplicate_term', $term->term_id, $tr_term_id, $lang );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $tr_term_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media translation id
|
||||
* Create the translation if it does not exist
|
||||
* Attach the media to the parent post
|
||||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param int $id Media ID.
|
||||
* @return int Translated media ID.
|
||||
*/
|
||||
protected function translate_media( $id ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $tr_id = $this->model->post->get( $id, $this->target_language ) ) {
|
||||
$tr_id = $this->model->post->create_media_translation( $id, $this->target_language );
|
||||
}
|
||||
|
||||
// If we don't have a translation and did not success to create one, return current media
|
||||
if ( empty( $tr_id ) ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
// Attach to the translated post
|
||||
if ( ! wp_get_post_parent_id( $tr_id ) && 0 < $this->target_post->ID ) {
|
||||
// Query inspired by wp_media_attach_action()
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent = %d WHERE post_type = 'attachment' AND ID = %d", $this->target_post->ID, $tr_id ) );
|
||||
clean_attachment_cache( $tr_id );
|
||||
}
|
||||
|
||||
return $tr_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the 'gallery' and 'playlist' shortcodes
|
||||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param array $attr Shortcode attributes.
|
||||
* @param null $null Shortcode content, not used.
|
||||
* @param string $tag Shortcode tag (either 'gallery' or 'playlist').
|
||||
* @return string Translated shortcode.
|
||||
*/
|
||||
public function ids_list_shortcode( $attr, $null, $tag ) {
|
||||
$out = array();
|
||||
|
||||
foreach ( $attr as $k => $v ) {
|
||||
if ( 'ids' === $k ) {
|
||||
$ids = explode( ',', $v );
|
||||
$tr_ids = array();
|
||||
foreach ( $ids as $id ) {
|
||||
$tr_ids[] = $this->translate_media( (int) $id );
|
||||
}
|
||||
$v = implode( ',', $tr_ids );
|
||||
}
|
||||
$out[] = $k . '="' . $v . '"';
|
||||
}
|
||||
|
||||
return '[' . $tag . ' ' . implode( ' ', $out ) . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the caption shortcode
|
||||
* Compatible only with the new style introduced in WP 3.4
|
||||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param array $attr Shortcode attrbute.
|
||||
* @param string $content Shortcode content.
|
||||
* @param string $tag Shortcode tag (either 'caption' or 'wp-caption').
|
||||
* @return string Translated shortcode.
|
||||
*/
|
||||
public function caption_shortcode( $attr, $content, $tag ) {
|
||||
// Translate the caption id
|
||||
$out = array();
|
||||
|
||||
foreach ( $attr as $k => $v ) {
|
||||
if ( 'id' === $k ) {
|
||||
$idarr = explode( '_', $v );
|
||||
$id = $idarr[1]; // Remember this
|
||||
$tr_id = $idarr[1] = $this->translate_media( (int) $id );
|
||||
$v = implode( '_', $idarr );
|
||||
}
|
||||
$out[] = $k . '="' . $v . '"';
|
||||
}
|
||||
|
||||
// Translate the caption content
|
||||
if ( ! empty( $id ) && ! empty( $tr_id ) ) {
|
||||
$p = get_post( (int) $id );
|
||||
$tr_p = get_post( $tr_id );
|
||||
if ( $p && $tr_p ) {
|
||||
$content = str_replace( $p->post_excerpt, $tr_p->post_excerpt, $content );
|
||||
}
|
||||
}
|
||||
|
||||
return '[' . $tag . ' ' . implode( ' ', $out ) . ']' . $content . '[/' . $tag . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate images and caption in inner html
|
||||
*
|
||||
* Since 2.5
|
||||
*
|
||||
* @param string $content HTML string.
|
||||
* @return string
|
||||
*/
|
||||
protected function translate_html( $content ) {
|
||||
if ( $this->options['media_support'] ) {
|
||||
$textarr = wp_html_split( $content ); // Since 4.2.3
|
||||
|
||||
$img_ids = array();
|
||||
foreach ( $textarr as $i => $text ) {
|
||||
// Translate img class and alternative text
|
||||
if ( 0 === strpos( $text, '<img' ) ) {
|
||||
$img_ids[] = $this->translate_img( $textarr[ $i ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $img_ids ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$new_content = implode( $textarr );
|
||||
$key = 0;
|
||||
$new_content = preg_replace_callback(
|
||||
'@(?<before><figcaption.*?>)(.+?)(?<after></figcaption>)@',
|
||||
function ( $matches ) use ( $img_ids, &$key ) {
|
||||
$tr_post = get_post( $img_ids[ $key ] );
|
||||
$key++;
|
||||
if ( ! empty( $tr_post->post_excerpt ) ) {
|
||||
return $matches['before'] . $tr_post->post_excerpt . $matches['after'];
|
||||
} else {
|
||||
return $matches[0];
|
||||
}
|
||||
},
|
||||
$new_content
|
||||
);
|
||||
|
||||
if ( is_string( $new_content ) ) {
|
||||
return $new_content;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates <img> 'class' and 'alt' attributes.
|
||||
*
|
||||
* @since 1.9
|
||||
* @since 2.5 The html is passed by reference and the return value is the image ID.
|
||||
*
|
||||
* @param string $text Reference to <img> html with attributes.
|
||||
* @return null|int Translated image id if exist.
|
||||
*/
|
||||
protected function translate_img( &$text ) {
|
||||
$attributes = wp_kses_attr_parse( $text ); // since WP 4.2.3
|
||||
|
||||
if ( ! is_array( $attributes ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Replace class
|
||||
foreach ( $attributes as $k => $attr ) {
|
||||
if ( 0 === strpos( $attr, 'class' ) && preg_match( '#wp\-image\-([0-9]+)#', $attr, $matches ) && ! empty( $matches[1] ) ) {
|
||||
$tr_id = $this->translate_media( (int) $matches[1] );
|
||||
$attributes[ $k ] = str_replace( 'wp-image-' . $matches[1], 'wp-image-' . $tr_id, $attr );
|
||||
|
||||
}
|
||||
|
||||
if ( preg_match( '#^data\-id="([0-9]+)#', $attr, $matches ) && ! empty( $matches[1] ) ) {
|
||||
$tr_id = $this->translate_media( (int) $matches[1] );
|
||||
$attributes[ $k ] = str_replace( 'data-id="' . $matches[1], 'data-id="' . $tr_id, $attr );
|
||||
}
|
||||
|
||||
if ( 0 === strpos( $attr, 'data-link' ) && preg_match( '#attachment_id=([0-9]+)#', $attr, $matches ) && ! empty( $matches[1] ) ) {
|
||||
$tr_id = $this->translate_media( (int) $matches[1] );
|
||||
$attributes[ $k ] = str_replace( 'attachment_id=' . $matches[1], 'attachment_id=' . $tr_id, $attr );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $tr_id ) ) {
|
||||
// Got a tr_id, attempt to replace the alt text
|
||||
$alt = get_post_meta( $tr_id, '_wp_attachment_image_alt', true );
|
||||
if ( is_string( $alt ) && ! empty( $alt ) ) {
|
||||
foreach ( $attributes as $k => $attr ) {
|
||||
if ( 0 === strpos( $attr, 'alt' ) ) {
|
||||
$attributes[ $k ] = 'alt="' . esc_attr( $alt ) . '" ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$text = implode( $attributes );
|
||||
|
||||
return empty( $tr_id ) ? null : $tr_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively translate blocks.
|
||||
*
|
||||
* @param array[] $blocks An array of arrays representing a block.
|
||||
* @return array
|
||||
*/
|
||||
protected function translate_blocks( $blocks ) {
|
||||
foreach ( $blocks as $k => $block ) {
|
||||
switch ( $block['blockName'] ) {
|
||||
case 'core/block':
|
||||
if ( ! $this->model->is_translated_post_type( 'wp_block' ) || ! isset( $block['attrs']['ref'] ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$tr_id = $this->model->post->get( $block['attrs']['ref'], $this->target_language );
|
||||
|
||||
if ( ! empty( $tr_id ) ) {
|
||||
$blocks[ $k ]['attrs']['ref'] = $tr_id;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'core/latest-posts':
|
||||
if ( isset( $block['attrs']['categories'] ) ) {
|
||||
$tr_ids = array();
|
||||
foreach ( $block['attrs']['categories'] as $term ) {
|
||||
$tr_ids[] = $this->model->term->get( $term['id'], $this->target_language );
|
||||
}
|
||||
|
||||
// Let's remove unfound translation results.
|
||||
$tr_ids = array_filter( $tr_ids );
|
||||
|
||||
// If there is no translation, then the category is unset.
|
||||
if ( empty( $tr_ids ) ) {
|
||||
unset( $blocks[ $k ]['attrs']['categories'] );
|
||||
break;
|
||||
}
|
||||
|
||||
// Query all the translated terms outside the loop to avoid multiple SQL queries with get_term() call.
|
||||
$terms = get_terms( array( 'include' => $tr_ids, 'hide_empty' => false, 'fields' => 'id=>name' ) );
|
||||
|
||||
if ( ! is_array( $terms ) ) {
|
||||
unset( $blocks[ $k ]['attrs']['categories'] );
|
||||
break;
|
||||
}
|
||||
|
||||
$tr_data = array();
|
||||
foreach ( $terms as $id => $term_name ) {
|
||||
$tr_data[] = array(
|
||||
'id' => $id,
|
||||
'value' => $term_name,
|
||||
);
|
||||
}
|
||||
if ( $tr_data ) {
|
||||
$blocks[ $k ]['attrs']['categories'] = $tr_data;
|
||||
} else {
|
||||
unset( $blocks[ $k ]['attrs']['categories'] );
|
||||
}
|
||||
} else {
|
||||
unset( $blocks[ $k ]['attrs']['categories'] );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $this->options['media_support'] ) {
|
||||
$blocks[ $k ] = $this->translate_media_block( $blocks[ $k ] );
|
||||
}
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$blocks[ $k ]['innerBlocks'] = $this->translate_blocks( $block['innerBlocks'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters parsed blocks after core blocks have been translated.
|
||||
*
|
||||
* @since 2.5.3
|
||||
*
|
||||
* @param array[] $blocks List of blocks.
|
||||
* @param string $lang Language of target.
|
||||
* @param string $from_lang Language of the source.
|
||||
*/
|
||||
return apply_filters( 'pll_translate_blocks', $blocks, $this->target_language->slug, $this->from_language->slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates media ids in blocks.
|
||||
*
|
||||
* @since 3.3
|
||||
*
|
||||
* @param array $block A representative array of a block.
|
||||
* @return array The translated block.
|
||||
*/
|
||||
protected function translate_media_block( $block ) {
|
||||
switch ( $block['blockName'] ) {
|
||||
case 'core/audio':
|
||||
case 'core/video':
|
||||
if ( array_key_exists( 'id', $block['attrs'] ) ) {
|
||||
$block['attrs']['id'] = $this->translate_media( $block['attrs']['id'] );
|
||||
}
|
||||
break;
|
||||
case 'core/cover':
|
||||
case 'core/image':
|
||||
if ( array_key_exists( 'id', $block['attrs'] ) ) {
|
||||
$block['attrs']['id'] = $this->translate_media( $block['attrs']['id'] );
|
||||
}
|
||||
$block = $this->translate_block_content( $block );
|
||||
break;
|
||||
|
||||
case 'core/file':
|
||||
$source_id = $block['attrs']['id'];
|
||||
$tr_id = $this->translate_media( $source_id );
|
||||
$block['attrs']['id'] = $tr_id;
|
||||
$textarr = wp_html_split( $block['innerHTML'] );
|
||||
$source_post = get_post( $source_id );
|
||||
if ( ! $source_post instanceof WP_Post ) {
|
||||
break;
|
||||
}
|
||||
$replace_file_link_text = 0 === strpos( $textarr[3], '<a' ) && $textarr[4] === $source_post->post_title;
|
||||
if ( $replace_file_link_text ) {
|
||||
$tr_post = get_post( $tr_id );
|
||||
if ( $tr_post ) {
|
||||
$textarr[4] = $tr_post->post_title;
|
||||
$block['innerContent'][0] = implode( $textarr );
|
||||
$block['innerHTML'] = implode( $textarr );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'core/gallery':
|
||||
if ( isset( $block['attrs']['ids'] ) && is_array( $block['attrs']['ids'] ) ) {
|
||||
// Backward compatibility with WP < 5.9.
|
||||
foreach ( $block['attrs']['ids'] as $n => $id ) {
|
||||
$block['attrs']['ids'][ $n ] = $this->translate_media( $id );
|
||||
}
|
||||
}
|
||||
$block = $this->translate_block_content( $block );
|
||||
break;
|
||||
|
||||
case 'core/media-text':
|
||||
$block['attrs']['mediaId'] = $this->translate_media( $block['attrs']['mediaId'] );
|
||||
$block['innerContent'][0] = $this->translate_html( $block['innerContent'][0] );
|
||||
break;
|
||||
|
||||
case 'core/shortcode':
|
||||
$block['innerContent'][0] = do_shortcode( $block['innerContent'][0] );
|
||||
$block['innerHTML'] = do_shortcode( $block['innerHTML'] );
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( ! empty( $block['innerHTML'] ) ) {
|
||||
$block = $this->translate_block_content( $block );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the block properties with a translation if it is found.
|
||||
*
|
||||
* @since 2.9
|
||||
*
|
||||
* @param array $block An array mimicking the structure of {@see https://github.com/WordPress/WordPress/blob/5.5.1/wp-includes/class-wp-block-parser.php WP_Block_Parser_Block}.
|
||||
* @return array The updated array formatted block.
|
||||
*/
|
||||
protected function translate_block_content( $block ) {
|
||||
$inner_content_nb = count( $block['innerContent'] );
|
||||
for ( $i = 0; $i < $inner_content_nb; $i++ ) {
|
||||
if ( ! empty( $block['innerContent'][ $i ] ) ) {
|
||||
$html = do_shortcode( $block['innerContent'][ $i ] ); // Translate shortcodes.
|
||||
$html = $this->translate_html( $html ); // Translate inline images.
|
||||
|
||||
$block['innerContent'][ $i ] = $html;
|
||||
}
|
||||
}
|
||||
$html = do_shortcode( $block['innerHTML'] ); // Translate shortcodes.
|
||||
$block['innerHTML'] = $this->translate_html( $html );
|
||||
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AutoPoly - AI Translation For Polylang (Pro)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles cloning and translation of taxonomy terms for AI Translation For Polylang.
|
||||
*
|
||||
* Provides methods to translate taxonomy terms (categories, tags, custom taxonomies)
|
||||
* using translation entries, and to assign parent relationships for translated terms.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class ATFP_Term_Clone {
|
||||
|
||||
/**
|
||||
* Main model for managing languages and translations.
|
||||
*
|
||||
* @var ATFP_Model
|
||||
*/
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* Constructor for ATFP_Term_Clone.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param ATFP_Settings|ATFP_Admin $polylang Main plugin object containing model and sync references.
|
||||
*/
|
||||
public function __construct( &$polylang ) {
|
||||
$this->model = &$polylang->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a taxonomy term using translation entries.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $entry Array containing term properties and translation data.
|
||||
* @param PLL_Language $target_language Target language object.
|
||||
* @return int|WP_Error The translated term ID, or WP_Error on failure.
|
||||
*/
|
||||
public function translate( array $entry, PLL_Language $target_language ) {
|
||||
if ( ! $entry['data'] instanceof Translations ) {
|
||||
/* translators: %d is a term ID. */
|
||||
return new WP_Error( 'atfp_translate_term_no_translations', sprintf( __( 'The term with ID %d could not be translated.', 'auto-translate-for-polylang' ), (int) $entry['id'] ) );
|
||||
}
|
||||
|
||||
$source_term = get_term( $entry['id'] );
|
||||
|
||||
if ( ! $source_term instanceof WP_Term ) {
|
||||
/* translators: %d is a term ID. */
|
||||
return new WP_Error( 'atfp_translate_term_no_source_term', sprintf( __( 'The term with ID %d could not be translated as it doesn\'t exist.', 'auto-translate-for-polylang' ), (int) $entry['id'] ) );
|
||||
}
|
||||
|
||||
$tr_term_name = $this->get_translated_term_name( $source_term, $entry['data'] );
|
||||
$tr_term_description = $this->get_translated_term_description( $source_term, $entry['data'] );
|
||||
$tr_term_id = $this->model->term->get( $entry['id'], $target_language );
|
||||
$tr_term_slug = $this->get_translated_term_slug( $source_term, $entry['data'] );
|
||||
|
||||
if ( $tr_term_id ) {
|
||||
// The translation already exists.
|
||||
$args = array();
|
||||
// Only update name or description if provided in translations.
|
||||
if ( $source_term->name !== $tr_term_name ) {
|
||||
$args['name'] = $tr_term_name;
|
||||
}
|
||||
if ( !empty($source_term->description) && $source_term->description !== $tr_term_description ) {
|
||||
$args['description'] = $tr_term_description;
|
||||
}
|
||||
|
||||
$tr_term = $this->model->term->update( $tr_term_id, $args );
|
||||
if ( is_wp_error( $tr_term ) ) {
|
||||
/* translators: %d is a term ID. */
|
||||
return new WP_Error( 'atfp_translate_update_term_failed', sprintf( __( 'The term with ID %d could not be updated.', 'auto-translate-for-polylang' ), (int) $tr_term_id ) );
|
||||
}
|
||||
} else {
|
||||
$args = array(
|
||||
'translations' => $this->model->term->get_translations( $source_term->term_id ),
|
||||
);
|
||||
|
||||
if ( !empty( $source_term->description ) ) {
|
||||
$args['description'] = $tr_term_description;
|
||||
}
|
||||
|
||||
if ( $tr_term_slug && ! empty( $tr_term_slug ) ) {
|
||||
$args['slug'] = $tr_term_slug;
|
||||
}
|
||||
|
||||
$tr_term = $this->model->term->insert( $tr_term_name, $source_term->taxonomy, $target_language, $args );
|
||||
if ( is_wp_error( $tr_term ) ) {
|
||||
/* translators: %d is a term ID. */
|
||||
return new WP_Error( 'atfp_translate_term_failed', sprintf( __( 'The term with ID %d could not be translated.', 'auto-translate-for-polylang' ), (int) $entry['id'] ) );
|
||||
}
|
||||
$tr_term_id = (int) $tr_term['term_id'];
|
||||
}
|
||||
|
||||
/** @var WP_Term $tr_term */
|
||||
$tr_term = get_term( $tr_term_id );
|
||||
|
||||
/**
|
||||
* Fires after a term is saved and its translations are updated.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int $tr_term_id The translated term ID.
|
||||
* @param string $taxonomy The taxonomy slug.
|
||||
* @param array $translations Array of term translations.
|
||||
*/
|
||||
do_action( 'atfp_save_term', $tr_term_id, $source_term->taxonomy, $this->model->term->get_translations( $tr_term_id ) );
|
||||
|
||||
$this->assign_parents( [ $entry['id'] ], $target_language );
|
||||
|
||||
return $tr_term_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated term name, or falls back to the source name.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param WP_Term $source_term The source term object.
|
||||
* @param Translations $translations Translation data object.
|
||||
* @return string The translated name.
|
||||
*/
|
||||
private function get_translated_term_name( WP_Term $source_term, Translations $translations ) {
|
||||
$entry = new Translation_Entry( array( 'singular' => $source_term->name, 'context' => 'name' ) );
|
||||
|
||||
$translated_entry = $translations->translate_entry( $entry );
|
||||
|
||||
$translated_text = isset( $translated_entry->translation[0] ) && ! empty( $translated_entry->translation[0] ) ? $translated_entry->translation[0] : $source_term->name;
|
||||
|
||||
return $translated_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated term description, or falls back to the source description.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param WP_Term $source_term The source term object.
|
||||
* @param Translations $translations Translation data object.
|
||||
* @return string The translated description.
|
||||
*/
|
||||
private function get_translated_term_description( WP_Term $source_term, Translations $translations ) {
|
||||
$entry = new Translation_Entry( array( 'singular' => $source_term->description, 'context' => 'description' ) );
|
||||
|
||||
$translated_entry = $translations->translate_entry( $entry );
|
||||
|
||||
$translated_text = isset( $translated_entry->translation[0] ) && ! empty( $translated_entry->translation[0] ) ? $translated_entry->translation[0] : '';
|
||||
|
||||
return $translated_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated term slug, or returns an empty string if not available.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param WP_Term $source_term The source term object.
|
||||
* @param Translations $translations Translation data object.
|
||||
* @return string The translated slug.
|
||||
*/
|
||||
private function get_translated_term_slug( WP_Term $source_term, Translations $translations ) {
|
||||
$entry = new Translation_Entry( array( 'singular' => $source_term->slug, 'context' => 'slug' ) );
|
||||
|
||||
$translated_entry = $translations->translate_entry( $entry );
|
||||
|
||||
$translated_text = isset( $translated_entry->translation[0] ) && ! empty( $translated_entry->translation[0] ) ? $translated_entry->translation[0] : '';
|
||||
|
||||
return $translated_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns parent terms to translated terms after import.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int[] $ids Array of source term IDs.
|
||||
* @param PLL_Language $target_language Target language object.
|
||||
* @return void
|
||||
*/
|
||||
public function assign_parents( array $ids, PLL_Language $target_language ) {
|
||||
// Get the terms with their parents (or 0).
|
||||
$terms = get_terms(
|
||||
array(
|
||||
'include' => $ids,
|
||||
'hide_empty' => false,
|
||||
'fields' => 'id=>parent',
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_array( $terms ) ) {
|
||||
// No terms with parents.
|
||||
return;
|
||||
}
|
||||
|
||||
// ‘id=>parent’ returns an array of numeric strings, so let's cast it into int.
|
||||
$terms = array_map( 'intval', array_filter( $terms, 'is_numeric' ) );
|
||||
|
||||
// Keep only the terms that have a parent.
|
||||
$terms = array_filter( $terms );
|
||||
|
||||
if ( empty( $terms ) ) {
|
||||
// No terms with parents.
|
||||
return;
|
||||
}
|
||||
|
||||
$tr_ids = array();
|
||||
foreach ( $terms as $child => $term_id ) {
|
||||
$tr_ids[ $child ] = $this->model->term->get( $child, $target_language->slug );
|
||||
}
|
||||
$tr_ids = array_filter( $tr_ids );
|
||||
|
||||
if ( empty( $tr_ids ) ) {
|
||||
// No translations.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $terms as $child => $term_id ) {
|
||||
if ( empty( $tr_ids[ $child ] ) ) {
|
||||
// Not translated.
|
||||
continue;
|
||||
}
|
||||
|
||||
$tr_parent_term = $this->model->term->get( $term_id, $target_language->slug );
|
||||
if ( empty( $tr_parent_term ) ) {
|
||||
// The parent term is not translated.
|
||||
continue;
|
||||
}
|
||||
|
||||
$tr_term_id = $this->model->term->get( $tr_ids[ $child ], $target_language->slug );
|
||||
if ( empty( $tr_term_id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tr_term = get_term( $tr_term_id );
|
||||
if ( ! $tr_term instanceof WP_Term ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set term parent for shared slugs.
|
||||
$this->model->term->update( $tr_term->term_id, array( 'parent' => $tr_parent_term ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user