first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
<?php
/**
* Class WPML_Nav_Menu_Actions
*
* @package wpml-core
* @subpackage taxonomy-term-translation
*/
class WPML_Nav_Menu_Actions extends WPML_Full_Translation_API {
/**
* @param SitePress $sitepress
* @param wpdb $wpdb
* @param WPML_Post_Translation $post_translations
* @param WPML_Term_Translation $term_translations
*/
public function __construct( &$sitepress, &$wpdb, &$post_translations, &$term_translations ) {
parent::__construct( $sitepress, $wpdb, $post_translations, $term_translations );
add_action( 'wp_delete_nav_menu', array( $this, 'wp_delete_nav_menu' ) );
add_action( 'wp_create_nav_menu', array( $this, 'wp_update_nav_menu' ), 10, 2 );
add_action( 'wp_update_nav_menu', array( $this, 'wp_update_nav_menu' ), 10, 2 );
add_action( 'wp_update_nav_menu_item', array( $this, 'wp_update_nav_menu_item' ), 10, 3 );
add_action( 'delete_post', array( $this, 'wp_delete_nav_menu_item' ) );
add_filter( 'pre_update_option_theme_mods_' . get_option( 'stylesheet' ), array( $this, 'pre_update_theme_mods_theme' ) );
if ( is_admin() ) {
add_filter( 'theme_mod_nav_menu_locations', array( $this, 'theme_mod_nav_menu_locations' ) );
}
}
public function wp_delete_nav_menu( $id ) {
$menu_id_tt = $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT term_taxonomy_id FROM {$this->wpdb->term_taxonomy} WHERE term_id=%d AND taxonomy='nav_menu'",
$id
)
);
$update_args = array(
'element_id' => $menu_id_tt,
'element_type' => 'tax_nav_menu',
'context' => 'tax',
);
do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'before_delete' ) ) );
$q = "DELETE FROM {$this->wpdb->prefix}icl_translations WHERE element_id=%d AND element_type='tax_nav_menu' LIMIT 1";
$q_prepared = $this->wpdb->prepare( $q, $menu_id_tt );
$this->wpdb->query( $q_prepared );
do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'after_delete' ) ) );
}
function wp_update_nav_menu( $menu_id, $menu_data = null ) {
if ( $menu_data ) {
$trid = $this->get_trid_from_post_data();
$language_code = $this->get_save_lang( $menu_id );
$menu_id_tt = $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT term_taxonomy_id FROM {$this->wpdb->term_taxonomy} WHERE term_id=%d AND taxonomy='nav_menu' LIMIT 1",
$menu_id
)
);
$this->term_translations->reload();
$this->sitepress->set_element_language_details( $menu_id_tt, 'tax_nav_menu', $trid, $language_code );
}
}
function wp_update_nav_menu_item( $menu_id, $menu_item_db_id, $args ) {
$menu_lang = $this->term_translations->lang_code_by_termid( $menu_id );
$trid = $this->post_translations->get_element_trid( $menu_item_db_id );
if ( array_key_exists( 'menu-item-type', $args )
&& ( $args['menu-item-type'] === 'post_type' || $args['menu-item-type'] === 'taxonomy' )
&& array_key_exists( 'menu-item-object-id', $args )
&& $menu_id > 0
) {
$language_code_item = $args['menu-item-type'] === 'post_type'
? $this->post_translations->get_element_lang_code( $args['menu-item-object-id'] )
: $this->term_translations->lang_code_by_termid( $args['menu-item-object-id'] );
$language_code_item = $language_code_item ? $language_code_item : $this->sitepress->get_current_language();
if ( $language_code_item !== $menu_lang ) {
wp_remove_object_terms( (int) $menu_item_db_id, (int) $menu_id, 'nav_menu' );
}
}
$language_code = isset( $language_code_item ) && $language_code_item
? $language_code_item : ( $menu_lang ? $menu_lang : $this->sitepress->get_current_language() );
$this->sitepress->set_element_language_details( $menu_item_db_id, 'post_nav_menu_item', $trid, $language_code );
}
public function wp_delete_nav_menu_item( $menu_item_id ) {
$post = get_post( $menu_item_id );
if ( ! empty( $post->post_type ) && $post->post_type == 'nav_menu_item' ) {
$update_args = array(
'element_id' => $menu_item_id,
'element_type' => 'post_nav_menu_item',
'context' => 'post',
);
do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'before_delete' ) ) );
$q = "DELETE FROM {$this->wpdb->prefix}icl_translations WHERE element_id=%d AND element_type='post_nav_menu_item' LIMIT 1";
$q_prepared = $this->wpdb->prepare( $q, $menu_item_id );
$this->wpdb->query( $q_prepared );
do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'after_delete' ) ) );
}
}
public function pre_update_theme_mods_theme( $val ) {
$default_language = $this->sitepress->get_default_language();
$current_language = $this->sitepress->get_current_language();
if ( isset( $val['nav_menu_locations'] )
&& filter_input( INPUT_GET, 'action' ) === 'delete'
&& $current_language !== $default_language
) {
$val['nav_menu_locations'] = get_theme_mod( 'nav_menu_locations' );
}
if ( isset( $val['nav_menu_locations'] ) ) {
foreach ( (array) $val['nav_menu_locations'] as $k => $v ) {
if ( ! $v && $current_language !== $default_language ) {
$tl = get_theme_mod( 'nav_menu_locations' );
if ( isset( $tl[ $k ] ) ) {
$val['nav_menu_locations'][ $k ] = $tl[ $k ];
}
} else {
$val['nav_menu_locations'][ $k ] = icl_object_id(
$val['nav_menu_locations'][ $k ],
'nav_menu',
true,
$default_language
);
}
}
}
return $val;
}
public function theme_mod_nav_menu_locations( $theme_locations ) {
if ( is_admin() && (bool) $theme_locations === true ) {
$current_lang = $this->sitepress->get_current_language();
foreach ( (array) $theme_locations as $location => $menu_id ) {
$translated_menu_id = $this->term_translations->term_id_in( $menu_id, $current_lang );
if ( $translated_menu_id ) {
$theme_locations[ $location ] = $translated_menu_id;
}
}
}
return $theme_locations;
}
private function get_save_lang( $menu_id ) {
$language_code = isset( $_POST['icl_nav_menu_language'] )
? $_POST['icl_nav_menu_language'] : $this->term_translations->lang_code_by_termid( $menu_id );
$language_code = $language_code ? $language_code : $this->sitepress->get_current_language();
return $language_code;
}
/**
* @return bool|int|mixed|null|string
*/
private function get_trid_from_post_data() {
$trid = null;
if ( ! empty( $_POST['icl_translation_of'] ) && $_POST['icl_translation_of'] !== 'none' ) {
$trid = $this->sitepress->get_element_trid( $_POST['icl_translation_of'], 'tax_nav_menu' );
return $trid;
} elseif ( isset( $_POST['icl_nav_menu_trid'] ) ) {
$trid = ( (int) $_POST['icl_nav_menu_trid'] );
return $trid;
}
return $trid;
}
}

View File

@@ -0,0 +1,103 @@
<?php
class WPML_Frontend_Tax_Filters {
public function __construct() {
add_filter( 'taxonomy_template', array( $this, 'slug_template' ) );
add_filter( 'category_template', array( $this, 'slug_template' ) );
add_filter( 'tag_template', array( $this, 'slug_template' ) );
}
/**
* Adjust template (taxonomy-)$taxonomy-$term.php for translated term slugs and IDs
*
* @since 3.1
*
* @param string $template
*
* @return string The template filename if found.
*/
function slug_template( $template ) {
global $sitepress;
$term = $this->get_queried_tax_term();
if ( $term === false || ! is_taxonomy_translated( $term->taxonomy ) ) {
return $template;
}
$templates = array();
$has_filter = remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
$current_language = $sitepress->get_current_language();
$default_language = $sitepress->get_default_language();
$templates = $this->add_term_templates( $term, $current_language, $templates );
$templates = $this->add_original_term_templates( $term, $default_language, $current_language, $templates );
if ( ! in_array( $term->taxonomy, array( 'category', 'post_tag' ), true ) ) {
$templates[] = 'taxonomy-' . $current_language . '.php';
$templates[] = 'taxonomy.php';
}
if ( $has_filter ) {
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
}
$new_template = locate_template( array_unique( $templates ) );
$template = $new_template ? $new_template : $template;
return $template;
}
private function get_template_prefix( $taxonomy ) {
$prefix = in_array( $taxonomy, array( 'category', 'post_tag' ), true ) ? '' : 'taxonomy-';
$prefix .= $taxonomy == 'post_tag' ? 'tag' : $taxonomy;
return $prefix;
}
private function add_term_templates( $term, $current_language, $templates ) {
$prefix = $this->get_template_prefix( $term->taxonomy );
$templates[] = "$prefix-{$current_language}-{$term->slug}.php";
$templates[] = "$prefix-{$current_language}-{$term->term_id}.php";
$templates[] = "$prefix-{$current_language}.php";
$templates[] = "$prefix-{$term->slug}.php";
$templates[] = "$prefix-{$term->term_id}.php";
return $templates;
}
private function add_original_term_templates( $term, $default_language, $current_language, $templates ) {
$taxonomy = $term->taxonomy;
$prefix = $this->get_template_prefix( $taxonomy );
$original_term_id = icl_object_id( $term->term_id, $taxonomy, true, $default_language );
$original_term = get_term_by( 'id', $original_term_id, $taxonomy );
if ( $original_term ) {
$templates[] = "$prefix-{$current_language}-{$original_term->slug}.php";
$templates[] = "$prefix-{$current_language}-{$original_term_id}.php";
$templates[] = "$prefix-{$original_term->slug}.php";
$templates[] = "$prefix-{$original_term_id}.php";
$templates[] = "$prefix-{$current_language}.php";
$templates[] = "$prefix.php";
}
return $templates;
}
private function get_queried_tax_term() {
global $wp_query;
/** @var WP_Query $wp_query */
$term = $wp_query->get_queried_object();
$res = false;
if ( (bool) $term === true && isset( $term->taxonomy ) && $term->taxonomy ) {
$res = $term;
}
return $res;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* WPML_Term_Filters class file.
*
* @package WPML\Core
* @subpackage taxonomy-term-translation
*/
/**
* Class WPML_Term_Filters
*/
class WPML_Term_Filters extends WPML_WPDB_And_SP_User {
/**
* Init class.
*/
public function init() {
$taxonomies = get_taxonomies();
foreach ( $taxonomies as $taxonomy ) {
$this->add_hooks_to_translated_taxonomy( $taxonomy );
}
add_action( 'registered_taxonomy', [ $this, 'registered_taxonomy' ], 10, 3 );
}
/**
* @param string $taxonomy Taxonomy slug.
* @param array|string $object_type Object type or array of object types.
* @param array $taxonomy_object Array of taxonomy registration arguments.
*/
public function registered_taxonomy( $taxonomy, $object_type, $taxonomy_object ) {
$this->add_hooks_to_translated_taxonomy( $taxonomy );
}
/**
* @param string $taxonomy Taxonomy slug.
*/
private function add_hooks_to_translated_taxonomy( $taxonomy ) {
if ( is_taxonomy_translated( $taxonomy ) ) {
add_filter( "pre_option_{$taxonomy}_children", [ $this, 'pre_option_tax_children' ], 10, 0 );
add_action( "create_{$taxonomy}", [ $this, 'update_tax_children_option' ], 10, 0 );
add_action( "edit_{$taxonomy}", [ $this, 'update_tax_children_option' ], 10, 0 );
}
}
public function update_tax_children_option( $taxonomy_input = false ) {
global $wpml_language_resolution, $wp_taxonomies;
$language_codes = $wpml_language_resolution->get_active_language_codes();
$language_codes[] = 'all';
$taxonomy = str_replace( array( 'create_', 'edit_' ), '', current_action() );
$taxonomy = isset( $wp_taxonomies[ $taxonomy ] ) ? $taxonomy : $taxonomy_input;
foreach ( $language_codes as $lang ) {
$tax_children = $this->get_tax_hier_array( $taxonomy, $lang );
$option_key = "{$taxonomy}_children_{$lang}";
update_option( $option_key, $tax_children );
}
}
public function pre_option_tax_children() {
$taxonomy = str_replace( array( 'pre_option_', '_children' ), '', current_filter() );
$lang = $this->sitepress->get_current_language();
$option_key = "{$taxonomy}_children_{$lang}";
$tax_children = get_option( $option_key, false );
if ( $tax_children === false ) {
$tax_children = $this->get_tax_hier_array( $taxonomy, $lang );
update_option( $option_key, $tax_children );
}
return ! empty( $tax_children ) ? $tax_children : false;
}
/**
* @param string $taxonomy
* @param string $lang_code
*
* @return array
*/
public function get_tax_hier_array( $taxonomy, $lang_code ) {
$hierarchy = array();
if ( $lang_code != 'all' ) {
$terms = $this->wpdb->get_results(
$this->wpdb->prepare(
"SELECT term_id, parent
FROM {$this->wpdb->term_taxonomy} tt
JOIN {$this->wpdb->prefix}icl_translations iclt
ON tt.term_taxonomy_id = iclt.element_id
WHERE tt.parent > 0
AND tt.taxonomy = %s
AND iclt.language_code = %s
AND iclt.element_type = %s
ORDER BY term_id",
$taxonomy,
$lang_code,
'tax_' . $taxonomy
)
);
} else {
$terms = $this->wpdb->get_results(
$this->wpdb->prepare(
"SELECT term_id, parent
FROM {$this->wpdb->term_taxonomy} tt
WHERE tt.parent > 0
AND tt.taxonomy = %s
ORDER BY term_id",
$taxonomy
)
);
}
foreach ( $terms as $term ) {
if ( $term->parent > 0 ) {
$hierarchy[ $term->parent ] = isset( $hierarchy[ $term->parent ] )
? $hierarchy[ $term->parent ] : array();
$hierarchy[ $term->parent ][] = $term->term_id;
}
}
return $hierarchy;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* Class WPML_Term_Hierarchy_Duplication
*
* @package wpml-core
* @subpackage taxonomy-term-translation
*/
class WPML_Term_Hierarchy_Duplication extends WPML_WPDB_And_SP_User {
public function duplicates_require_sync( $post_ids, $duplicates_only = true ) {
$taxonomies = $this->sitepress->get_translatable_taxonomies( true );
foreach ( $taxonomies as $key => $tax ) {
if ( ! is_taxonomy_hierarchical( $tax ) ) {
unset( $taxonomies[ $key ] );
}
}
if ( (bool) $post_ids === true ) {
$need_sync_taxonomies = $duplicates_only === true
? $this->get_need_sync_new_dupl( $post_ids, $taxonomies )
: $this->get_need_sync_all_terms( $taxonomies, $post_ids );
} else {
$need_sync_taxonomies = array();
}
return array_values( array_unique( $need_sync_taxonomies ) );
}
private function get_need_sync_new_dupl( $duplicated_ids, $taxonomies ) {
$new_terms = $this->get_new_terms_just_duplicated( $duplicated_ids, $taxonomies );
$affected_taxonomies = array();
foreach ( $new_terms as $term ) {
$affected_taxonomies[] = $term->taxonomy;
}
$affected_taxonomies = array_unique( $affected_taxonomies );
$hierarchy_sync_helper = wpml_get_hierarchy_sync_helper( 'term' );
$unsynced_terms = $hierarchy_sync_helper->get_unsynced_elements(
$affected_taxonomies,
$this->sitepress->get_default_language()
);
foreach ( $new_terms as $key => $new_term ) {
$sync = true;
foreach ( $unsynced_terms as $term_unsynced ) {
if ( $term_unsynced->translated_id == $new_term->term_taxonomy_id ) {
$sync = false;
break;
}
}
if ( $sync === true ) {
unset( $new_terms[ $key ] );
}
}
$need_sync_taxonomies = array();
foreach ( $new_terms as $term ) {
$need_sync_taxonomies[] = $term->taxonomy;
}
return $need_sync_taxonomies;
}
private function get_need_sync_all_terms( $translated_taxonomies, $post_ids ) {
$hierarchy_sync_helper = wpml_get_hierarchy_sync_helper( 'term' );
$post_ids_in = wpml_prepare_in( (array) $post_ids, '%d' );
$taxonomies_in = wpml_prepare_in( $translated_taxonomies );
$this->wpdb->get_col(
"SELECT DISTINCT tt.taxonomy
FROM {$this->wpdb->term_taxonomy} tt
JOIN {$this->wpdb->term_relationships} tr
ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tr.object_id IN ({$post_ids_in}) AND tt.taxonomy IN ({$taxonomies_in})"
);
foreach ( $translated_taxonomies as $key => $tax ) {
$unsynced_terms = $hierarchy_sync_helper->get_unsynced_elements(
$tax,
$this->sitepress->get_default_language()
);
if ( (bool) $unsynced_terms === false ) {
unset( $translated_taxonomies[ $key ] );
}
}
return $translated_taxonomies;
}
private function get_new_terms_just_duplicated( $duplicate_ids, $taxonomies ) {
if ( (bool) $duplicate_ids === false || (bool) $taxonomies === false ) {
return array();
}
$duplicate_ids_in = wpml_prepare_in( $duplicate_ids, '%d' );
$taxonomies_in = wpml_prepare_in( $taxonomies );
$terms = $this->wpdb->get_results(
"SELECT tt.term_taxonomy_id, tt.taxonomy
FROM {$this->wpdb->term_taxonomy} tt
JOIN {$this->wpdb->term_relationships} tr
ON tt.term_taxonomy_id = tr.term_taxonomy_id
JOIN {$this->wpdb->postmeta} pm
ON pm.post_id = tr.object_id
JOIN {$this->wpdb->terms} t_duplicate
ON t_duplicate.term_id = tt.term_id
JOIN {$this->wpdb->terms} t_original
ON t_original.name = t_duplicate.name
JOIN {$this->wpdb->term_taxonomy} tt_master
ON tt_master.term_id = t_original.term_id
JOIN {$this->wpdb->term_relationships} tr_master
ON tt_master.term_taxonomy_id = tr_master.term_taxonomy_id
LEFT JOIN {$this->wpdb->term_relationships} tr_other
ON tt.term_taxonomy_id = tr_other.term_taxonomy_id
AND tr_other.object_id != tr.object_id
AND tr_other.object_id NOT IN ({$duplicate_ids_in})
LEFT JOIN {$this->wpdb->postmeta} pm_other
ON pm_other.post_id = tr_other.object_id
AND NOT (pm_other.meta_key = '_icl_lang_duplicate_of'
AND pm_other.meta_value IN ({$duplicate_ids_in}))
WHERE pm.meta_key = '_icl_lang_duplicate_of'
AND tr_other.object_id IS NULL
AND pm_other.post_id IS NULL
AND pm.meta_value IN ({$duplicate_ids_in})
AND tr_master.object_id IN ({$duplicate_ids_in})
AND tt.taxonomy IN ({$taxonomies_in})"
);
return $terms;
}
}

View File

@@ -0,0 +1,47 @@
<?php
class WPML_Term_Hierarchy_Sync extends WPML_Hierarchy_Sync {
protected $element_id_column = 'term_taxonomy_id';
protected $parent_id_column = 'parent';
protected $parent_element_id_column = 'term_id';
protected $element_type_column = 'taxonomy';
protected $element_type_prefix = 'tax_';
/**
* @param wpdb $wpdb
*/
public function __construct( &$wpdb ) {
parent::__construct( $wpdb );
$this->elements_table = $wpdb->term_taxonomy;
}
public function is_need_sync( $taxonomy, $ref_lang = false ) {
return (bool) $this->get_unsynced_elements( $taxonomy, $ref_lang );
}
public function sync_element_hierarchy( $element_types, $ref_lang = false ) {
/** @var WPML_Term_Filters $wpml_term_filters_general */
global $wpml_term_filters_general;
parent::sync_element_hierarchy( $element_types, $ref_lang );
do_action( 'wpml_sync_term_hierarchy_done' );
$element_types = (array) $element_types;
foreach ( $element_types as $taxonomy ) {
$wpml_term_filters_general->update_tax_children_option( $taxonomy );
}
}
/**
* @param string $element_type
*
* @return bool
*/
public function is_hierarchical( $element_type ) {
return is_taxonomy_hierarchical( $element_type );
}
}

View File

@@ -0,0 +1,106 @@
<?php
use WPML\FP\Fns;
class WPML_Term_Translation_Utils extends WPML_SP_User {
/**
* Duplicates all terms, that exist in the given target language,
* from the original post to the translation in that language.
*
* @param int $original_post_id
* @param string $lang
*/
function sync_terms( $original_post_id, $lang ) {
$this->synchronize_terms( $original_post_id, $lang, false );
}
/**
* Duplicates all terms on the original post to its translation in the given target language.
* Missing terms are created with the same name as their originals.
*
* @param int $original_post_id
* @param string $lang
*/
function duplicate_terms( $original_post_id, $lang ) {
$this->synchronize_terms( $original_post_id, $lang, true );
}
/**
* @param int $original_post_id
* @param string $lang
* @param bool $duplicate sets whether missing terms should be created by duplicating the original term
*/
private function synchronize_terms( $original_post_id, $lang, $duplicate ) {
global $wpml_post_translations;
$returnTrue = Fns::always( true );
add_filter( 'wpml_disable_term_adjust_id', $returnTrue );
$wpml_post_translations->reload();
$translated_post_id = $wpml_post_translations->element_id_in( $original_post_id, $lang );
if ( (bool) $translated_post_id === true ) {
$taxonomies = get_post_taxonomies( $original_post_id );
foreach ( $taxonomies as $tax ) {
$terms_on_original = wp_get_object_terms( $original_post_id, $tax );
if ( ! $this->sitepress->is_translated_taxonomy( $tax ) ) {
if ( $this->sitepress->get_setting( 'sync_post_taxonomies' ) ) {
// Taxonomy is not translated so we can just copy from the original
foreach ( $terms_on_original as $key => $term ) {
$terms_on_original[ $key ] = $term->term_id;
}
wp_set_object_terms( $translated_post_id, $terms_on_original, $tax );
}
} else {
/** @var int[] $translated_terms translated term_ids */
$translated_terms = $this->get_translated_term_ids( $terms_on_original, $lang, $tax, $duplicate );
wp_set_object_terms( $translated_post_id, $translated_terms, $tax );
}
}
}
remove_filter( 'wpml_disable_term_adjust_id', $returnTrue );
clean_object_term_cache( $original_post_id, get_post_type( $original_post_id ) );
}
/**
* @param object[] $terms
* @param string $lang
* @param string $taxonomy
* @param bool $duplicate sets whether missing terms should be created by duplicating the original term
*
* @return array
*/
private function get_translated_term_ids( $terms, $lang, $taxonomy, $duplicate ) {
/** @var WPML_Term_Translation $wpml_term_translations */
global $wpml_term_translations;
$term_utils = new WPML_Terms_Translations();
$wpml_term_translations->reload();
$translated_terms = array();
foreach ( $terms as $orig_term ) {
$translated_id = (int) $wpml_term_translations->term_id_in( $orig_term->term_id, $lang );
if ( ! $translated_id && $duplicate ) {
$translation = $term_utils->create_automatic_translation(
array(
'lang_code' => $lang,
'taxonomy' => $taxonomy,
'trid' => $wpml_term_translations->get_element_trid( $orig_term->term_taxonomy_id ),
'source_language' => $wpml_term_translations->get_element_lang_code(
$orig_term->term_taxonomy_id
),
)
);
$translated_id = isset( $translation['term_id'] ) ? $translation['term_id'] : false;
}
if ( $translated_id ) {
$translated_terms[] = $translated_id;
}
}
return $translated_terms;
}
}

View File

@@ -0,0 +1,168 @@
<?php
/**
* @since 3.2
*
* Class WPML_Term_Translation
*
* Provides APIs for translating taxonomy terms
*
* @package wpml-core
* @subpackage taxonomy-term-translation
*/
class WPML_Term_Translation extends WPML_Element_Translation {
/** @var array|null */
protected $ttids;
/** @var array|null */
protected $term_ids;
public function reload() {
parent::reload();
$this->term_ids = null;
$this->ttids = null;
}
/**
* @param int $term_id
*
* @return null|string
*/
public function lang_code_by_termid( $term_id ) {
return $this->get_element_lang_code( $this->adjust_ttid_for_term_id( $term_id ) );
}
/**
* Converts term_id into term_taxonomy_id
*
* @param int $term_id
*
* @return int
*/
public function adjust_ttid_for_term_id( $term_id ) {
$this->maybe_warm_term_id_cache();
return $term_id && isset( $this->ttids[ $term_id ] ) ? end( $this->ttids[ $term_id ] ) : $term_id;
}
/**
* Converts term_taxonomy_id into term_id
*
* @param int $ttid term_taxonomy_id
*
* @return int
*/
public function adjust_term_id_for_ttid( $ttid ) {
$this->maybe_warm_term_id_cache();
return $ttid && isset( $this->term_ids[ $ttid ] ) ? $this->term_ids[ $ttid ] : $ttid;
}
/**
* @param int $term_id
* @param string $lang_code
* @param bool|false $original_fallback if true will return the the input term_id in case no translation is found
*
* @return null|int
*/
public function term_id_in( $term_id, $lang_code, $original_fallback = false ) {
return $this->adjust_term_id_for_ttid(
$this->element_id_in( $this->adjust_ttid_for_term_id( $term_id ), $lang_code, $original_fallback )
);
}
/**
* Returns the trid for a given term_id and taxonomy or null on failure
*
* @param int $term_id term_id of a term
* @param string $taxonomy taxonomy of the term
*
* @return null|int
*/
public function trid_from_tax_and_id( $term_id, $taxonomy ) {
$this->maybe_warm_term_id_cache();
$ttid = $term_id && isset( $this->ttids[ $term_id ][ $taxonomy ] )
? $this->ttids[ $term_id ][ $taxonomy ] : $term_id;
return $this->get_element_trid( $ttid );
}
/**
* Returns all post types to which a taxonomy is linked.
*
* @param string $taxonomy
*
* @return array
*
* @since 3.2.3
*/
public function get_taxonomy_post_types( $taxonomy ) {
return WPML_WP_Taxonomy::get_linked_post_types( $taxonomy );
}
protected function get_element_join() {
return "FROM {$this->wpdb->prefix}icl_translations wpml_translations
JOIN {$this->wpdb->term_taxonomy} tax
ON wpml_translations.element_id = tax.term_taxonomy_id
AND wpml_translations.element_type = CONCAT('tax_', tax.taxonomy)";
}
protected function get_type_prefix() {
return 'tax_';
}
private function maybe_warm_term_id_cache() {
if ( ! isset( $this->ttids ) || ! isset( $this->term_ids ) ) {
$data = $this->wpdb->get_results(
' SELECT wpml_translations.element_id, tax.term_id, tax.taxonomy
' . $this->get_element_join() . "
JOIN {$this->wpdb->terms} terms
ON terms.term_id = tax.term_id
WHERE tax.term_id != tax.term_taxonomy_id",
ARRAY_A
);
$this->term_ids = array();
$this->ttids = array();
foreach ( $data as $row ) {
$this->ttids[ $row['term_id'] ] = isset( $this->ttids[ $row['term_id'] ] )
? $this->ttids[ $row['term_id'] ] : array();
$this->ttids[ $row['term_id'] ][ $row['taxonomy'] ] = $row['element_id'];
$this->term_ids[ $row['element_id'] ] = $row['term_id'];
}
}
}
/**
* @param string $term
* @param string $slug
* @param string $taxonomy
* @param string $lang_code
*
* @return string
*/
public function generate_unique_term_slug( $term, $slug, $taxonomy, $lang_code ) {
if ( '' === trim( $slug ) ) {
$slug = sanitize_title( $term );
}
return WPML_Terms_Translations::term_unique_slug( $slug, $taxonomy, $lang_code );
}
/**
* @return self
*/
public static function getGlobalInstance() {
global $wpml_term_translations, $wpdb;
if ( ! isset( $wpml_term_translations ) ) {
$wpml_term_translations = new WPML_Term_Translation( $wpdb );
}
return $wpml_term_translations;
}
}

View File

@@ -0,0 +1,719 @@
<?php
require_once dirname( __FILE__ ) . '/wpml-update-term-action.class.php';
use WPML\FP\Lst;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\FP\Logic;
use function WPML\FP\pipe;
/**
* @since 3.1.8
*
* Class WPML_Terms_Translations
*
* This class holds some basic functionality for translating taxonomy terms.
*
* @package wpml-core
* @subpackage taxonomy-term-translation
*/
class WPML_Terms_Translations {
/**
* @param array<string|\WP_Term> $terms
* @param string[]|string $taxonomies This is only used by the WP core AJAX call that fetches the preview
* auto-complete for flat taxonomy term adding
*
* @return array<\WP_Term>
* @deprecated since Version 3.1.8.3
*/
public static function get_terms_filter( $terms, $taxonomies ) {
global $wpdb, $sitepress;
$lang = $sitepress->get_current_language();
foreach ( $taxonomies as $taxonomy ) {
if ( $sitepress->is_translated_taxonomy( $taxonomy ) ) {
$element_type = 'tax_' . $taxonomy;
$query = $wpdb->prepare(
"SELECT wptt.term_id
FROM {$wpdb->prefix}icl_translations AS iclt
JOIN {$wpdb->prefix}term_taxonomy AS wptt
ON iclt.element_id = wptt.term_taxonomy_id
WHERE language_code=%s AND element_type = %s",
$lang,
$element_type
);
$element_ids_array = $wpdb->get_col( $query );
foreach ( $terms as $key => $term ) {
if ( ! is_object( $term ) ) {
$term = get_term_by( 'name', $term, $taxonomy );
}
if ( $term && isset( $term->taxonomy )
&& $term->taxonomy === $taxonomy
&& ! in_array( $term->term_id, $element_ids_array ) ) {
unset( $terms[ $key ] );
}
}
}
}
return $terms;
}
/**
* @param string $slug
* @param string $taxonomy
* @param string $lang
* Creates a unique slug for a given term, using a scheme
* encoding the language code in the slug.
*
* @return string
*/
public static function term_unique_slug( $slug, $taxonomy, $lang ) {
global $sitepress;
$default_language = $sitepress->get_default_language();
if ( $lang !== $default_language && self::term_slug_exists( $slug, $taxonomy ) ) {
$slug .= '-' . $lang;
}
$i = 2;
$suffix = '-' . $i;
if ( self::term_slug_exists( $slug, $taxonomy ) ) {
while ( self::term_slug_exists( $slug . $suffix, $taxonomy ) ) {
$i ++;
$suffix = '-' . $i;
}
$slug .= $suffix;
}
return $slug;
}
/**
* @param string $slug
* @param bool $taxonomy
* If $taxonomy is given, then slug existence is checked only for the specific taxonomy.
*
* @return bool
*/
private static function term_slug_exists( $slug, $taxonomy = false ) {
global $wpdb;
$existing_term_prepared_query = $wpdb->prepare(
"SELECT t.term_id
FROM {$wpdb->terms} t
JOIN {$wpdb->term_taxonomy} tt
ON t.term_id = tt.term_id
WHERE t.slug = %s
AND tt.taxonomy = %s
LIMIT 1",
$slug,
$taxonomy
);
$term_id = $wpdb->get_var( $existing_term_prepared_query );
return (bool) $term_id;
}
/**
* This function provides an action hook only used by WCML.
* It will be removed in the future and should not be implemented in new spots.
*
* @deprecated deprecated since version 3.1.8.3
*
* @param string $taxonomy The identifier of the taxonomy the translation was just saved to.
* @param array $translated_term The associative array holding term taxonomy id and term id,
* as returned by wp_insert_term or wp_update_term.
*/
public static function icl_save_term_translation_action( $taxonomy, $translated_term ) {
global $wpdb, $sitepress;
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
$term_taxonomy_id = $translated_term['term_taxonomy_id'];
$original_ttid = $sitepress->get_original_element_id( $term_taxonomy_id, 'tax_' . $taxonomy );
$original_tax_sql = "SELECT * FROM {$wpdb->term_taxonomy} WHERE taxonomy=%s AND term_taxonomy_id = %d";
$original_tax_prepared = $wpdb->prepare( $original_tax_sql, array( $taxonomy, $original_ttid ) );
$original_tax = $wpdb->get_row( $original_tax_prepared );
do_action( 'icl_save_term_translation', $original_tax, $translated_term );
}
}
/**
* Prints a hidden div, containing the list of allowed terms for a post type in each language.
* This is used to only display the correct categories and tags in the quick-edit fields of the post table.
*
* @param string $column_name
* @param string|string[]|\WP_Post $post_type
*/
public static function quick_edit_terms_removal( $column_name, $post_type ) {
global $sitepress, $wpdb;
if ( $column_name == 'icl_translations' ) {
$taxonomies = array_filter(
get_object_taxonomies( $post_type ),
array(
$sitepress,
'is_translated_taxonomy',
)
);
$terms_by_language_and_taxonomy = array();
if ( ! empty( $taxonomies ) ) {
$res = $wpdb->get_results(
" SELECT language_code, taxonomy, term_id FROM {$wpdb->term_taxonomy} tt
JOIN {$wpdb->prefix}icl_translations wpml_translations
ON wpml_translations.element_id = tt.term_taxonomy_id
AND wpml_translations.element_type = CONCAT('tax_', tt.taxonomy)
WHERE tt.taxonomy IN (" . wpml_prepare_in( $taxonomies ) . ' )'
);
} else {
$res = array();
}
foreach ( $res as $term ) {
$lang = $term->language_code;
$tax = $term->taxonomy;
$terms_by_language_and_taxonomy[ $lang ] = isset( $terms_by_language_and_taxonomy[ $lang ] ) ? $terms_by_language_and_taxonomy[ $lang ] : array();
$terms_by_language_and_taxonomy[ $lang ][ $tax ] = isset( $terms_by_language_and_taxonomy[ $lang ][ $tax ] ) ? $terms_by_language_and_taxonomy[ $lang ][ $tax ] : array();
$terms_by_language_and_taxonomy[ $lang ][ $tax ][] = $term->term_id;
}
$terms_json = wp_json_encode( $terms_by_language_and_taxonomy );
$output = '<div id="icl-terms-by-lang" style="display: none;">' . wp_kses_post( $terms_json ) . '</div>';
echo $output;
}
}
/**
* Creates a new term from an argument array.
*
* @param array $args
* @return array|bool
* Returns either an array containing the term_id and term_taxonomy_id of the term resulting from this database
* write or false on error.
*/
public static function create_new_term( $args ) {
global $wpdb, $sitepress;
/** @var string $taxonomy */
$taxonomy = false;
/** @var string $lang_code */
$lang_code = false;
/**
* Sets whether translations of posts are to be updated by the newly created term,
* should they be missing a translation still.
* During debug actions designed to synchronise post and term languages this should not be set to true,
* doing so introduces the possibility of removing terms from posts before switching
* them with their translation in the correct language.
*
* @var bool
*/
$sync = false;
extract( $args, EXTR_OVERWRITE );
require_once dirname( __FILE__ ) . '/wpml-update-term-action.class.php';
$new_term_action = new WPML_Update_Term_Action( $wpdb, $sitepress, $args );
$new_term = $new_term_action->execute();
if ( $sync && $new_term && $taxonomy && $lang_code ) {
self::sync_taxonomy_terms_language( $taxonomy );
}
return $new_term;
}
/**
* @param array<mixed> $args
* Creates an automatic translation of a term, the name of which is set as "original" . @ "lang_code" and the slug of which is set as "original_slug" . - . "lang_code".
*
* @return array|bool
*/
public function create_automatic_translation( $args ) {
global $sitepress;
$term = false;
$lang_code = false;
$taxonomy = false;
$original_id = false;
$original_tax_id = false;
$trid = false;
$original_term = false;
$update_translations = false;
$source_language = null;
extract( $args, EXTR_OVERWRITE );
if ( $trid && ! $original_id ) {
$original_tax_id = $sitepress->get_original_element_id_by_trid( $trid );
$original_term = get_term_by( 'term_taxonomy_id', $original_tax_id, $taxonomy, OBJECT, 'no' );
}
if ( $original_id && ! $original_tax_id ) {
$original_term = get_term( $original_id, $taxonomy, OBJECT, 'no' );
if ( isset( $original_term['term_taxonomy_id'] ) ) {
$original_tax_id = $original_term['term_taxonomy_id'];
}
}
if ( ! $trid ) {
$trid = $sitepress->get_element_trid( $original_tax_id, 'tax_' . $taxonomy );
}
if ( ! $source_language ) {
$source_language = $sitepress->get_source_language_by_trid( $trid );
}
$existing_translations = $sitepress->get_element_translations( $trid, 'tax_' . $taxonomy );
if ( $lang_code && isset( $existing_translations[ $lang_code ] ) ) {
$new_translated_term = false;
} else {
if ( ! $original_term ) {
if ( $original_id ) {
$original_term = get_term( $original_id, $taxonomy, OBJECT, 'no' );
} elseif ( $original_tax_id ) {
$original_term = get_term_by( 'term_taxonomy_id', $original_tax_id, $taxonomy, OBJECT, 'no' );
}
}
$translated_slug = false;
if ( ! $term && isset( $original_term->name ) ) {
$term = $original_term->name;
/**
* @deprecated use 'wpml_duplicate_generic_string' instead, with the same arguments
*/
$term = apply_filters(
'icl_duplicate_generic_string',
$term,
$lang_code,
array(
'context' => 'taxonomy',
'attribute' => $taxonomy,
'key' => $original_term->term_id,
)
);
$term = apply_filters(
'wpml_duplicate_generic_string',
$term,
$lang_code,
array(
'context' => 'taxonomy',
'attribute' => $taxonomy,
'key' => $original_term->term_id,
)
);
}
if ( isset( $original_term->slug ) ) {
$translated_slug = $original_term->slug;
/**
* @deprecated use 'wpml_duplicate_generic_string' instead, with the same arguments
*/
$translated_slug = apply_filters(
'icl_duplicate_generic_string',
$translated_slug,
$lang_code,
array(
'context' => 'taxonomy_slug',
'attribute' => $taxonomy,
'key' => $original_term->term_id,
)
);
$translated_slug = apply_filters(
'wpml_duplicate_generic_string',
$translated_slug,
$lang_code,
array(
'context' => 'taxonomy_slug',
'attribute' => $taxonomy,
'key' => $original_term->term_id,
)
);
$translated_slug = self::term_unique_slug( $translated_slug, $taxonomy, $lang_code );
}
$new_translated_term = false;
if ( $term ) {
$new_term_args = array(
'term' => $term,
'slug' => $translated_slug,
'taxonomy' => $taxonomy,
'lang_code' => $lang_code,
'original_tax_id' => $original_tax_id,
'update_translations' => $update_translations,
'trid' => $trid,
'source_language' => $source_language,
);
$new_translated_term = self::create_new_term( $new_term_args );
}
}
return $new_translated_term;
}
/**
* @param string $taxonomy
*
* Sets all taxonomy terms to the correct language on each post, having at least one term from the taxonomy.
*/
public static function sync_taxonomy_terms_language( $taxonomy ) {
$all_posts_in_taxonomy = get_posts( array( 'tax_query' => array( 'taxonomy' => $taxonomy ) ) );
foreach ( $all_posts_in_taxonomy as $post_in_taxonomy ) {
self::sync_post_and_taxonomy_terms_language( $post_in_taxonomy->ID, $taxonomy );
}
}
/**
* @param int $post_id
*
* Sets all taxonomy terms ot the correct language for a given post.
*/
public static function sync_post_terms_language( $post_id ) {
$taxonomies = get_taxonomies();
foreach ( $taxonomies as $taxonomy ) {
self::sync_post_and_taxonomy_terms_language( $post_id, $taxonomy );
}
}
/**
* @param int $post_id
* @param string $taxonomy
* Synchronizes a posts taxonomy term's languages with the posts language for all translations of the post.
*/
public static function sync_post_and_taxonomy_terms_language( $post_id, $taxonomy ) {
global $sitepress;
$post = get_post( $post_id );
$post_type = $post->post_type;
$post_trid = $sitepress->get_element_trid( $post_id, 'post_' . $post_type );
$post_translations = $sitepress->get_element_translations( $post_trid, 'post_' . $post_type );
$terms_from_original_post = wp_get_post_terms( $post_id, $taxonomy );
$is_original = true;
if ( $sitepress->get_original_element_id( $post_id, 'post_' . $post_type ) != $post_id ) {
$is_original = false;
}
foreach ( $post_translations as $post_language => $translated_post ) {
$translated_post_id = $translated_post->element_id;
if ( ! $translated_post_id ) {
continue;
}
$terms_from_translated_post = wp_get_post_terms( $translated_post_id, $taxonomy );
if ( $is_original ) {
$duplicates = $sitepress->get_duplicates( $post_id );
if ( in_array( $translated_post_id, $duplicates ) ) {
$terms = array_merge( $terms_from_original_post, $terms_from_translated_post );
} else {
$terms = $terms_from_translated_post;
}
} else {
$terms = $terms_from_translated_post;
}
foreach ( (array) $terms as $term ) {
$term_original_tax_id = $term->term_taxonomy_id;
$original_term_language_object = $sitepress->get_element_language_details( $term_original_tax_id, 'tax_' . $term->taxonomy );
if ( $original_term_language_object && isset( $original_term_language_object->language_code ) ) {
$original_term_language = $original_term_language_object->language_code;
} else {
$original_term_language = $post_language;
}
if ( $original_term_language != $post_language ) {
$term_trid = $sitepress->get_element_trid( $term_original_tax_id, 'tax_' . $term->taxonomy );
$translated_terms = $sitepress->get_element_translations( $term_trid, 'tax_' . $term->taxonomy, false, false, true );
$term_id = $term->term_id;
wp_remove_object_terms( $translated_post_id, (int) $term_id, $taxonomy );
if ( isset( $translated_terms[ $post_language ] ) ) {
$term_in_correct_language = $translated_terms[ $post_language ];
wp_set_post_terms( $translated_post_id, array( (int) $term_in_correct_language->term_id ), $taxonomy, true );
}
if ( isset( $term->term_taxonomy_id ) ) {
wp_update_term_count( $term->term_taxonomy_id, $taxonomy );
}
}
wp_update_term_count( $term_original_tax_id, $taxonomy );
}
}
}
/**
* @param int $post_id Object ID.
* @param array $terms An array of object terms.
* @param array $tt_ids An array of term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
* @param bool $append Whether to append new terms to the old terms.
* @param array $old_tt_ids Old array of term taxonomy IDs.
*/
public static function set_object_terms_action( $post_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) {
global $sitepress;
// TODO: [WPML 3.2] We have a better way to check if the post is an external type (e.g. Package).
if ( get_post( $post_id ) ) {
self::set_tags_in_proper_language( $post_id, $tt_ids, $taxonomy, $old_tt_ids );
if ( $sitepress->get_setting( 'sync_post_taxonomies' ) ) {
$term_actions_helper = $sitepress->get_term_actions_helper();
$term_actions_helper->added_term_relationships( $post_id );
}
}
}
/**
* @param int $post_id Object ID.
* @param array $tt_ids An array of term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
* @param array $old_tt_ids Old array of term taxonomy IDs.
* @param bool $isBulkEdit
*/
private static function set_tags_in_proper_language( $post_id, $tt_ids, $taxonomy, $old_tt_ids ) {
$isEditAction = isset( $_POST['action'] ) && ( 'editpost' === $_POST['action'] || 'inline-save' === $_POST['action'] );
$isBulkEdit = isset( $_REQUEST['bulk_edit'] );
if ( $isEditAction || $isBulkEdit ) {
$tt_ids = array_map( 'intval', $tt_ids );
$tt_ids = array_diff( $tt_ids, $old_tt_ids );
self::quick_edited_post_terms( $post_id, $taxonomy, $tt_ids );
}
}
/**
* @param int $post_id
* @param string $taxonomy
* @param array $changed_ttids
*
* Running this function will remove certain issues arising out of bulk adding of terms to posts of various languages.
* This case can result in situations in which the WP Core functionality adds a term to a post, before the language assignment
* operations of WPML are triggered. This leads to states in which terms can be assigned to a post even though their language
* differs from that of the post.
* This function behaves between hierarchical and flat taxonomies. Hierarchical terms from the wrong taxonomy are simply removed
* from the post. Flat terms are added with the same name but in the correct language.
* For flat terms this implies either the use of the existing term or the creation of a new one.
* This function uses wpdb queries instead of the WordPress API, it is therefore save to be run out of
* any language setting.
*/
public static function quick_edited_post_terms( $post_id, $taxonomy, $newlyAddedTermIds ) {
global $wpdb, $sitepress, $wpml_post_translations;
$postLang = $wpml_post_translations->get_element_lang_code( $post_id ) ?: Obj::prop( 'icl_post_language', $_POST );
if ( ! $sitepress->is_translated_taxonomy( $taxonomy ) || ! ( $postLang ) ) {
return;
}
$sql = "SELECT element_id FROM {$wpdb->prefix}icl_translations WHERE language_code = %s AND element_type = %s";
$termIdsInPostLang = $wpdb->get_col( $wpdb->prepare( $sql, $postLang, 'tax_' . $taxonomy ) );
$termIdsInPostLang = Fns::map( \WPML\FP\Cast::toInt(), $termIdsInPostLang );
$newlyCreatedTermIds = [];
$isInPostLang = Lst::includes( Fns::__, $termIdsInPostLang );
if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
$getTermInPostLang = Obj::prop( 'idInPostLang' );
$createTermIfNotExists = Logic::ifElse( $getTermInPostLang, Fns::identity(), self::createTerm( $taxonomy, $postLang ) );
$updateOrDeleteTermInPost = Logic::ifElse(
$getTermInPostLang,
self::updatePostTaxonomy( $post_id ),
pipe( Obj::prop( 'id' ), self::deletePostTaxonomy( $post_id ) )
);
$newlyCreatedTermIds = \wpml_collect( $newlyAddedTermIds )
->reject( $isInPostLang )
->map( self::appendTermName() )
->map( self::appendTermIdCounterpartInPostLang( $termIdsInPostLang, $taxonomy ) )
->map( $createTermIfNotExists )
->map( $updateOrDeleteTermInPost )
->all();
} else {
\wpml_collect( $newlyAddedTermIds )->reject( $isInPostLang )->each( self::deletePostTaxonomy( $post_id ) );
}
// Update term counts manually here, since using sql, will not trigger the updating of term counts automatically.
wp_update_term_count( array_merge( $newlyAddedTermIds, $newlyCreatedTermIds ), $taxonomy );
}
/**
* @param int $postId
*
* @return Closure
*/
private static function deletePostTaxonomy( $postId ) {
return function ( $termId ) use ( $postId ) {
global $wpdb;
$wpdb->delete(
$wpdb->term_relationships,
[
'object_id' => $postId,
'term_taxonomy_id' => $termId,
]
);
};
}
/**
* @return Closure
*/
private static function appendTermName() {
return function ( $termId ) {
global $wpdb;
$sql = "SELECT t.name FROM {$wpdb->terms} AS t JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE tt.term_taxonomy_id=%d";
$termName = $wpdb->get_var( $wpdb->prepare( $sql, $termId ) );
return [ 'id' => $termId, 'name' => $termName ];
};
}
/**
* @param array $termIdsInPostLang
* @param string $taxonomy
*
* @return Closure
*/
private static function appendTermIdCounterpartInPostLang( $termIdsInPostLang, $taxonomy ) {
return function ( $termData ) use ( $termIdsInPostLang, $taxonomy ) {
global $wpdb;
$idInPostLang = false;
if ( count( $termIdsInPostLang ) ) {
$in = wpml_prepare_in( $termIdsInPostLang, '%d' );
$sql = "
SELECT tt.term_taxonomy_id FROM {$wpdb->terms} AS t
JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
WHERE t.name=%s AND tt.taxonomy=%s AND tt.term_taxonomy_id IN ({$in})
";
$idInPostLang = $wpdb->get_var( $wpdb->prepare( $sql, $termData['name'], $taxonomy ) );
}
return Obj::assoc( 'idInPostLang', $idInPostLang, $termData );
};
}
/**
* @param string $taxonomy
* @param string $postLang
*
* @return Closure
*/
private static function createTerm( $taxonomy, $postLang ) {
return function ( $termData ) use ( $taxonomy, $postLang ) {
global $sitepress;
$idInCorrectId = false;
$newTerm = wp_insert_term( $termData['name'], $taxonomy, [ 'slug' => self::term_unique_slug( sanitize_title( $termData['name'] ), $taxonomy, $postLang ) ] );
if ( isset( $newTerm['term_taxonomy_id'] ) ) {
$idInCorrectId = $newTerm['term_taxonomy_id'];
$trid = $sitepress->get_element_trid( $termData['id'], 'tax_' . $taxonomy );
$sitepress->set_element_language_details( $idInCorrectId, 'tax_' . $taxonomy, $trid, $postLang );
}
return Obj::assoc( 'idInPostLang', $idInCorrectId, $termData );
};
}
/**
* @param int $postId
*
* @return Closure
*/
private static function updatePostTaxonomy( $postId ) {
return function ( $termData ) use ( $postId ) {
global $wpdb;
$wpdb->update(
$wpdb->term_relationships,
[ 'term_taxonomy_id' => $termData['idInPostLang'] ],
[
'object_id' => $postId,
'term_taxonomy_id' => $termData['id'],
]
);
};
}
/**
* Returns an array of all terms, that have a language suffix on them.
* This is used by troubleshooting functionality.
*
* @return array
*/
public static function get_all_terms_with_language_suffix() {
global $wpdb;
$lang_codes = $wpdb->get_col( "SELECT code FROM {$wpdb->prefix}icl_languages" );
/*
Build the expression to find all potential candidates for renaming.
* These must have the part "<space>@lang_code<space>" in them.
*/
$where_parts = array();
foreach ( $lang_codes as $key => $code ) {
$where_parts[ $key ] = "t.name LIKE '" . '% @' . esc_sql( $code ) . "%'";
}
$where = '(' . join( ' OR ', $where_parts ) . ')';
$terms_with_suffix = $wpdb->get_results( "SELECT t.name, t.term_id, tt.taxonomy FROM {$wpdb->terms} AS t JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id WHERE {$where}" );
$terms = array();
foreach ( $terms_with_suffix as $term ) {
if ( $term->name == WPML_Troubleshooting_Terms_Menu::strip_language_suffix( $term->name ) ) {
continue;
}
$term_id = $term->term_id;
$term_taxonomy_label = $term->taxonomy;
$taxonomy = get_taxonomy( $term->taxonomy );
if ( $taxonomy && isset( $taxonomy->labels ) && isset( $taxonomy->labels->name ) ) {
$term_taxonomy_label = $taxonomy->labels->name;
}
if ( isset( $terms[ $term_id ] ) && isset( $terms[ $term_id ]['taxonomies'] ) ) {
if ( ! in_array( $term_taxonomy_label, $terms[ $term_id ]['taxonomies'] ) ) {
$terms[ $term_id ]['taxonomies'][] = $term_taxonomy_label;
}
} else {
$terms[ $term_id ] = array(
'name' => $term->name,
'taxonomies' => array( $term_taxonomy_label ),
);
}
}
return $terms;
}
}

View File

@@ -0,0 +1,266 @@
<?php
/**
* Class WPML_Update_Term_Action
*
* This class holds the functionality for creating or editing a taxonomy term.
*
* @package wpml-core
* @subpackage taxonomy-term-translation
*/
class WPML_Update_Term_Action extends WPML_WPDB_And_SP_User {
/**
* TRUE if this object represents valid data for the update or creation of a term, false otherwise.
*
* @var bool
*/
private $is_valid = true;
/**
* TRUE if this object represents term update action, false if it represents a term creation action.
*
* @var bool
*/
private $is_update;
/**
* Argument array containing arguments in a format that can and is used as input to \wp_update_term or
* \wp_insert_term
*
* @var array
*/
private $wp_new_term_args = array();
/**
* The taxonomy in which this action takes place.
*
* @var string
*/
private $taxonomy;
/**
* Trid value in the icl_translations table to which this action is to be written.
*
* @var int
*/
private $trid;
/**
* Language of the term that is to result from this action.
*
* @var string
*/
private $lang_code;
/**
* Source language of the term that is to result from this action.
*
* @var string|null
*/
private $source_lang_code = null;
/**
* Array holding translations of the term created by this object prior to it's creation.
*
* @var array
*/
private $existing_translations = array();
/**
* The term id of the term to be updated or resulting from this action.
*
* @var int
*/
private $term_id;
/**
* This only gets set for update actions. In this case the new slug has to be compared with the old slug,
* to decide whether any slug name sanitation has to happen.
*
* @var string
*/
private $old_slug;
/**
* @param wpdb $wpdb
* @param SitePress $sitepress
* @param array $args
*/
public function __construct( &$wpdb, &$sitepress, $args ) {
parent::__construct( $wpdb, $sitepress );
/**
* Actual name of the term. Same as the name input argument to \wp_update_term or \wp_insert_term
*
* @var string|bool
*/
$term = false;
$slug = '';
$taxonomy = '';
/** @var string $lang_code */
$lang_code = '';
$trid = null;
/** @var int|bool $original_tax_id */
$original_tax_id = false;
/**
* Taxonomy_term_id of the parent element
*
* @var int
*/
$parent = 0;
$description = false;
$term_group = false;
$source_language = null;
extract( $args, EXTR_OVERWRITE );
// We cannot create a term unless we at least know its name
if ( (string) $term !== '' && $taxonomy ) {
$this->wp_new_term_args['name'] = $term;
$this->taxonomy = $taxonomy;
} else {
$this->is_valid = false;
return;
}
if ( $parent ) {
$this->wp_new_term_args['parent'] = $parent;
}
if ( $description ) {
$this->wp_new_term_args['description'] = $description;
}
if ( $term_group ) {
$this->wp_new_term_args['term_group'] = $term_group;
}
$this->wp_new_term_args['term_group'] = $term_group;
$this->is_valid = $this->set_language_information( $trid, $original_tax_id, $lang_code, $source_language );
$this->set_action_type();
if ( ! $this->is_update || ( $this->is_update && $slug != $this->old_slug && ! empty( $slug ) ) ) {
if ( trim( $slug ) == '' ) {
$slug = sanitize_title( $term );
}
$slug = WPML_Terms_Translations::term_unique_slug( $slug, $taxonomy, $lang_code );
$this->wp_new_term_args['slug'] = $slug;
}
}
/**
* Writes the term update or creation action saved in this object to the database.
*
* @return array|false
* Returns either an array containing the term_id and term_taxonomy_id of the term resulting from this database
* write or false on error.
*/
public function execute() {
global $sitepress;
$switch_lang = new WPML_Temporary_Switch_Language( $sitepress, $this->lang_code );
remove_action( 'create_term', array( $sitepress, 'create_term' ), 1 );
remove_action( 'edit_term', array( $sitepress, 'create_term' ), 1 );
add_action( 'create_term', array( $this, 'add_term_language_action' ), 1, 3 );
$new_term = false;
if ( $this->is_valid ) {
if ( $this->is_update && $this->term_id ) {
$new_term = wp_update_term( $this->term_id, $this->taxonomy, $this->wp_new_term_args );
} else {
$new_term = wp_insert_term( $this->wp_new_term_args['name'], $this->taxonomy, $this->wp_new_term_args );
}
}
add_action( 'create_term', array( $sitepress, 'create_term' ), 1, 3 );
add_action( 'edit_term', array( $sitepress, 'create_term' ), 1, 3 );
remove_action( 'create_term', array( $this, 'add_term_language_action' ), 1 );
if ( ! is_array( $new_term ) ) {
$new_term = false;
}
unset( $switch_lang );
return $new_term;
}
/**
* This action is to be hooked to the WP create_term and edit_term hooks.
* It sets the correct language information after a term is saved.
*
* @param int|string $term_id
* @param int|string $term_taxonomy_id
* @param string $taxonomy
*/
public function add_term_language_action( $term_id, $term_taxonomy_id, $taxonomy ) {
if ( $this->is_valid && ! $this->is_update && $this->taxonomy == $taxonomy ) {
$this->sitepress->set_element_language_details(
$term_taxonomy_id,
'tax_' . $taxonomy,
$this->trid,
$this->lang_code,
$this->source_lang_code
);
}
}
/**
* Sets the language variables for this object.
*
* @param bool|int $trid
* @param bool|int $original_tax_id
* @param string $lang_code
* @param bool|string $source_language
* @return bool True if the given language parameters allowed for determining valid language information, false
* otherwise.
*/
private function set_language_information( $trid, $original_tax_id, $lang_code, $source_language ) {
if ( ! $lang_code || ! $this->sitepress->is_active_language( $lang_code ) ) {
return false;
} else {
$this->lang_code = $lang_code;
}
if ( ! $trid && $original_tax_id ) {
$trid = $this->sitepress->get_element_trid( $original_tax_id, 'tax_' . $this->taxonomy );
}
if ( $trid ) {
$this->trid = $trid;
$this->existing_translations = $this->sitepress->get_element_translations( $trid, 'tax_' . $this->taxonomy );
foreach ( $this->existing_translations as $lang => $translation ) {
if ( $original_tax_id && isset( $translation->element_id ) && $translation->element_id == $original_tax_id && isset( $translation->language_code ) && $translation->language_code ) {
$this->source_lang_code = $translation->language_code;
break;
} elseif ( isset( $translation->language_code ) && $translation->language_code && ! $translation->source_language_code ) {
$this->source_lang_code = $translation->language_code;
}
}
}
return true;
}
/**
* Sets the action type of this object.
* In case of this action being an update the is_update flag is set true.
* Also the term_id of the existing term is saved in $this->term_id.
*/
private function set_action_type() {
if ( ! $this->trid ) {
$this->is_update = false;
} elseif ( isset( $this->existing_translations[ $this->lang_code ] ) ) {
$existing_db_entry = $this->existing_translations[ $this->lang_code ];
if ( isset( $existing_db_entry->element_id ) && $existing_db_entry->element_id ) {
// Term update actions need information about the term_id, not the term_taxonomy_id saved in the element_id column of icl_translations.
/** @var \stdClass $term */
$term = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT t.term_id, t.slug FROM {$this->wpdb->terms} AS t
JOIN {$this->wpdb->term_taxonomy} AS tt ON t.term_id=tt.term_id
WHERE term_taxonomy_id=%d",
$existing_db_entry->element_id
)
);
if ( $term->term_id && $term->slug ) {
$this->is_update = true;
$this->term_id = $term->term_id;
$this->old_slug = $term->slug;
} else {
$this->is_update = false;
}
} else {
$this->sitepress->delete_element_translation( $this->trid, 'tax_' . $this->taxonomy, $this->lang_code );
$this->is_update = false;
}
} else {
$this->is_update = false;
}
}
}