Files
doitinpoland.com/wp-content/plugins/sitepress-multilingual-cms/inc/wp-nav-menus/wpml-menu-sync-functionality.class.php
2023-09-12 21:41:04 +02:00

603 lines
19 KiB
PHP

<?php
use WPML\FP\Lst;
abstract class WPML_Menu_Sync_Functionality extends WPML_Full_Translation_API {
const STRING_CONTEXT_SUFFIX = ' menu';
const STRING_NAME_LABEL_PREFIX = 'Menu Item Label ';
const STRING_NAME_URL_PREFIX = 'Menu Item URL ';
private $menu_items_cache;
/**
* @param SitePress $sitepress
* @param wpdb $wpdb
* @param WPML_Post_Translation $post_translations
* @param WPML_Terms_Translations $term_translations
*/
function __construct( &$sitepress, &$wpdb, &$post_translations, &$term_translations ) {
parent::__construct( $sitepress, $wpdb, $post_translations, $term_translations );
$this->menu_items_cache = array();
}
function get_menu_items( $menu_id, $translations = true ) {
$key = $menu_id . '-';
if ( $translations ) {
$key .= 'trans';
} else {
$key .= 'no-trans';
}
if ( ! isset( $this->menu_items_cache[ $key ] ) ) {
if ( ! isset( $this->menu_items_cache[ $menu_id ] ) ) {
$this->menu_items_cache[ $menu_id ] = wp_get_nav_menu_items( (int) $menu_id );
}
$items = $this->menu_items_cache[ $menu_id ];
$menu_items = array();
foreach ( $items as $item ) {
$item->object_type = get_post_meta( $item->ID, '_menu_item_type', true );
$_item_add = array(
'ID' => $item->ID,
'menu_order' => $item->menu_order,
'parent' => $item->menu_item_parent,
'object' => $item->object,
'url' => $item->url,
'object_type' => $item->object_type,
'object_id' => empty( $item->object_id ) ? get_post_meta(
$item->ID,
'_menu_item_object_id',
true
) : $item->object_id,
'title' => $item->title,
'depth' => $this->get_menu_item_depth( $item->ID ),
);
if ( $translations ) {
$_item_add['translations'] = $this->get_menu_item_translations( $item, $menu_id );
}
$menu_items[ $item->ID ] = $_item_add;
}
$this->menu_items_cache[ $key ] = $menu_items;
}
return $this->menu_items_cache[ $key ];
}
function sync_menu_translations( $menu_trans_data, $menus ) {
global $wpdb;
foreach ( $menu_trans_data as $menu_id => $translations ) {
foreach ( $translations as $language => $name ) {
$_POST['icl_translation_of'] = $wpdb->get_var(
$wpdb->prepare(
" SELECT term_taxonomy_id
FROM {$wpdb->term_taxonomy}
WHERE term_id=%d
AND taxonomy='nav_menu'
LIMIT 1",
$menu_id
)
);
$_POST['icl_nav_menu_language'] = $language;
$menu_indentation = '';
$menu_increment = 0;
do {
$new_menu_id = wp_update_nav_menu_object(
0,
array(
'menu-name' => $name . $menu_indentation
. ( $menu_increment
? $menu_increment : '' ),
)
);
$menu_increment = $menu_increment != '' ? $menu_increment + 1 : 2;
$menu_indentation = '-';
} while ( is_wp_error( $new_menu_id ) && $menu_increment < 10 );
$menus[ $menu_id ]['translations'][ $language ] = array( 'id' => $new_menu_id );
}
}
return $menus;
}
/**
* @param \stdClass $item
* @param int $menu_id
*
* @return array
*/
function get_menu_item_translations( $item, $menu_id ) {
$languages = array_keys( $this->sitepress->get_active_languages() );
$item_translations = $this->post_translations->get_element_translations( $item->ID );
$languages = array_diff( $languages, array( $this->sitepress->get_default_language() ) );
$translations = array_fill_keys( $languages, false );
foreach ( $languages as $lang_code ) {
$item->object_type = property_exists( $item, 'object_type' ) ? $item->object_type : $item->type;
$translated_object_id = (int) icl_object_id(
$item->object_type === 'post_type_archive' ? $item->ID : $item->object_id,
Lst::includes( $item->object_type, [ 'custom', 'post_type_archive' ] ) ? 'nav_menu_item' : $item->object,
false,
$lang_code
);
if ( ! $translated_object_id && $item->object_type !== 'custom' && $item->object_type !== 'post_type_archive' ) {
continue;
}
$translated_object_title = '';
$translated_object_url = $item->url;
$icl_st_label_exists = true;
$icl_st_url_exists = true;
$label_changed = false;
$url_changed = false;
if ( $item->object_type === 'post_type' ) {
list( $translated_object_id, $item_translations ) = $this->maybe_reload_post_item(
$translated_object_id,
$item_translations,
$item,
$lang_code
);
$translated_object = get_post( $translated_object_id );
if ( $translated_object->post_status === 'trash' ) {
$translated_object_id = false;
} else {
$translated_object_title = $translated_object->post_title;
}
} elseif ( $item->object_type === 'taxonomy' ) {
$translated_object = get_term(
$translated_object_id,
get_post_meta( $item->ID, '_menu_item_object', true )
);
$translated_object_title = $translated_object->name;
} elseif ( $item->object_type === 'custom' ) {
$translated_object_title = $item->post_title;
if ( defined( 'WPML_ST_PATH' ) ) {
list( $translated_object_url, $translated_object_title, $url_changed, $label_changed ) = $this->st_actions(
$lang_code,
$menu_id,
$item,
$translated_object_id,
$translated_object_title,
$translated_object_url,
$icl_st_label_exists,
$icl_st_url_exists
);
}
} elseif ( $item->object_type === 'post_type_archive' ) {
if ( $translated_object_id ) {
$translated_object = get_post( $translated_object_id );
$translated_object_title = $translated_object->post_title;
} else {
$translated_object_title = $item->post_title;
}
}
$this->fix_assignment_to_menu( $item_translations, (int) $menu_id );
$this->fix_language_conflicts();
$translated_item_id = isset( $item_translations[ $lang_code ] ) ? (int) $item_translations[ $lang_code ] : false;
$item_depth = $this->get_menu_item_depth( $translated_item_id );
if ( $translated_item_id ) {
$translated_item = get_post( $translated_item_id );
$translated_object_title = ! empty( $translated_item->post_title ) && ! $icl_st_label_exists ? $translated_item->post_title : $translated_object_title;
$translate_item_parent_item_id = (int) get_post_meta(
$translated_item_id,
'_menu_item_menu_item_parent',
true
);
if ( $item->menu_item_parent > 0
&& $translate_item_parent_item_id != $this->post_translations->element_id_in(
$item->menu_item_parent,
$lang_code
)
) {
$translate_item_parent_item_id = 0;
$item_depth = 0;
}
$translation = array(
'menu_order' => $translated_item->menu_order,
'parent' => $translate_item_parent_item_id,
);
} else {
$translation = array(
'menu_order' => ( $item->object_type === 'custom' ? $item->menu_order : 0 ),
'parent' => 0,
);
}
$translation['ID'] = $translated_item_id;
$translation['depth'] = $item_depth;
$translation['parent_not_translated'] = $this->is_parent_not_translated( $item, $lang_code );
$translation['object'] = $item->object;
$translation['object_type'] = $item->object_type;
$translation['object_id'] = $translated_object_id;
$translation['title'] = $translated_object_title;
$translation['url'] = $translated_object_url;
$translation['target'] = $item->target;
$translation['classes'] = $item->classes;
$translation['xfn'] = $item->xfn;
$translation['attr-title'] = $item->attr_title;
$translation['label_changed'] = $label_changed;
$translation['url_changed'] = $url_changed;
$translation['label_missing'] = ! $icl_st_label_exists;
$translation['url_missing'] = ! $icl_st_url_exists;
$translations[ $lang_code ] = $translation;
}
return $translations;
}
/**
* Synchronises a page menu item's translations' trids according to the trids of the pages they link to.
*
* @param object $menu_item
*
* @return int number of affected menu item translations
*/
function sync_page_menu_item_trids( $menu_item ) {
$changed = 0;
if ( $menu_item->object_type === 'post_type' ) {
$translations = $this->post_translations->get_element_translations( $menu_item->ID );
if ( (bool) $translations === true ) {
get_post_meta( $menu_item->menu_item_parent, '_menu_item_object_id', true );
$orphans = $this->wpdb->get_results(
$this->get_page_orphan_sql(
array_keys( $translations ),
$menu_item->ID
)
);
if ( (bool) $orphans === true ) {
$trid = $this->post_translations->get_element_trid( $menu_item->ID );
foreach ( $orphans as $orphan ) {
$this->sitepress->set_element_language_details(
$orphan->element_id,
'post_nav_menu_item',
$trid,
$orphan->language_code
);
$changed ++;
}
}
}
}
return $changed;
}
/**
* @param int $menu_id
* @param bool $include_original
*
* @return bool|array
*/
function get_menu_translations( $menu_id, $include_original = false ) {
$languages = array_keys( $this->sitepress->get_active_languages() );
$translations = array();
foreach ( $languages as $lang_code ) {
if ( $include_original || $lang_code !== $this->sitepress->get_default_language() ) {
$menu_translated_id = $this->term_translations->term_id_in( $menu_id, $lang_code );
$menu_data = array();
if ( $menu_translated_id ) {
/** @var \stdClass $menu_object */
$menu_object = $this->wpdb->get_row(
$this->wpdb->prepare(
"
SELECT t.term_id, t.name
FROM {$this->wpdb->terms} t
JOIN {$this->wpdb->term_taxonomy} x
ON t.term_id = t.term_id
WHERE t.term_id = %d
AND x.taxonomy='nav_menu'
LIMIT 1",
$menu_translated_id
)
);
$current_lang = $this->sitepress->get_current_language();
$this->sitepress->switch_lang( $lang_code, false );
$menu_data = array(
'id' => $menu_object->term_id,
'name' => $menu_object->name,
'items' => $this->get_menu_items( $menu_translated_id, false ),
);
$this->sitepress->switch_lang( $current_lang, false );
}
$translations[ $lang_code ] = $menu_data;
}
}
return $translations;
}
protected function get_menu_name( $menu_id ) {
$menu = wp_get_nav_menu_object( $menu_id );
return $menu ? $menu->name : false;
}
/**
* @param int $menu_id
* @param string|false $language_code
*
* @return bool
*/
protected function get_translated_menu( $menu_id, $language_code = false ) {
$language_code = $language_code ? $language_code : $this->sitepress->get_default_language();
$menus = $this->get_menu_translations( $menu_id, true );
return isset( $menus[ $language_code ] ) ? $menus[ $language_code ] : false;
}
/**
* We need to register the string first in the default language
* to avoid it being "auto-registered" in English
*
* @param string $menu_name
* @param WP_Post|stdClass $item
* @param string $lang
* @param bool $has_label_translation
* @param bool $has_url_translation
*
* @return array
*/
protected function icl_t_menu_item( $menu_name, $item, $lang, &$has_label_translation, &$has_url_translation ) {
$default_lang = $this->sitepress->get_default_language();
$label = $item->post_title;
$url = $item->url;
if ( $lang !== $default_lang ) {
icl_register_string(
$menu_name . self::STRING_CONTEXT_SUFFIX,
self::STRING_NAME_LABEL_PREFIX . $item->ID,
$label,
false,
$default_lang
);
$label = icl_t(
$menu_name . self::STRING_CONTEXT_SUFFIX,
self::STRING_NAME_LABEL_PREFIX . $item->ID,
$label,
$has_label_translation,
true,
$lang
);
icl_register_string(
$menu_name . self::STRING_CONTEXT_SUFFIX,
self::STRING_NAME_URL_PREFIX . $item->ID,
$url,
false,
$default_lang
);
$url = icl_t(
$menu_name . self::STRING_CONTEXT_SUFFIX,
self::STRING_NAME_URL_PREFIX . $item->ID,
$url,
$has_url_translation,
true,
$lang
);
}
return array( $label, $url );
}
/**
* @param object $item
* @param string $lang_code
*
* @return int
*/
private function is_parent_not_translated( $item, $lang_code ) {
if ( $item->menu_item_parent > 0 ) {
$item_parent_object_id = get_post_meta( $item->menu_item_parent, '_menu_item_object_id', true );
$item_parent_object = get_post_meta( $item->menu_item_parent, '_menu_item_object', true );
$parent_element_type = $item_parent_object === 'custom' ? 'nav_menu_item' : $item_parent_object;
$parent_translated = icl_object_id(
$item_parent_object_id,
$parent_element_type,
false,
$lang_code
);
}
return isset( $parent_translated ) && ! $parent_translated ? 1 : 0;
}
private function get_page_orphan_sql( $existing_languages, $menu_item_id ) {
$wpdb = &$this->wpdb;
return $wpdb->prepare(
"SELECT it.element_id, it.language_code
FROM {$wpdb->prefix}icl_translations it
JOIN {$wpdb->posts} pt
ON pt.ID = it.element_id
AND pt.post_type = 'nav_menu_item'
AND it.element_type = 'post_nav_menu_item'
AND it.language_code NOT IN (" . wpml_prepare_in( $existing_languages ) . ")
JOIN {$wpdb->prefix}icl_translations io
ON io.element_id = %d
AND io.element_type = 'post_nav_menu_item'
AND io.trid != it.trid
JOIN {$wpdb->posts} po
ON po.ID = io.element_id
AND po.post_type = 'nav_menu_item'
JOIN {$wpdb->postmeta} mo
ON mo.post_id = po.ID
AND mo.meta_key = '_menu_item_object_id'
JOIN {$wpdb->postmeta} mt
ON mt.post_id = pt.ID
AND mt.meta_key = '_menu_item_object_id'
JOIN {$wpdb->prefix}icl_translations page_t
ON mt.meta_value = page_t.element_id
AND page_t.element_type = 'post_page'
JOIN {$wpdb->prefix}icl_translations page_o
ON mo.meta_value = page_o.element_id
AND page_o.trid = page_t.trid
WHERE ( SELECT COUNT(count.element_id)
FROM {$wpdb->prefix}icl_translations count
WHERE count.trid = it.trid ) = 1",
$menu_item_id
);
}
private function maybe_reload_post_item( $translated_object_id, $item_translations, $item, $lang_code ) {
if ( $this->sync_page_menu_item_trids( $item ) > 0 ) {
$item_translations = $this->post_translations->get_element_translations( $item->ID );
$translated_object_id = $this->post_translations->element_id_in(
$item->object_id,
$lang_code
);
$translated_object_id = $translated_object_id === null ? false : $translated_object_id;
}
return array( $translated_object_id, $item_translations );
}
private function get_menu_item_depth( $item_id ) {
$depth = 0;
do {
$object_parent = get_post_meta( $item_id, '_menu_item_menu_item_parent', true );
if ( $object_parent == $item_id ) {
$depth = 0;
break;
} elseif ( $object_parent ) {
$item_id = $object_parent;
$depth ++;
}
} while ( $object_parent > 0 );
return $depth;
}
private function st_actions( $lang_code,
$menu_id,
$item,
$translated_object_id,
$translated_object_title,
$translated_object_url,
&$icl_st_label_exists,
&$icl_st_url_exists ) {
if ( ! function_exists( 'icl_translate' ) ) {
require WPML_ST_PATH . '/inc/functions.php';
}
$this->sitepress->switch_lang( $lang_code );
$label_changed = false;
$url_changed = false;
$menu_name = $this->get_menu_name( $menu_id );
$translated_object_title_t = '';
$translated_object_url_t = '';
$translated_menu_id = $this->term_translations->term_id_in( $menu_id, $lang_code );
if ( function_exists( 'icl_t' ) ) {
list( $translated_object_title_t, $translated_object_url_t ) = $this->icl_t_menu_item(
$menu_name,
$item,
$lang_code,
$icl_st_label_exists,
$icl_st_url_exists
);
} else {
$translated_object_title_t = $item->post_title . ' @' . $lang_code;
$translated_object_url_t = $item->url;
}
$this->sitepress->switch_lang();
if ( $translated_object_id ) {
$translated_object = get_post( $translated_object_id );
$label_changed = $translated_object_title_t != $translated_object->post_title;
$url_changed = $translated_object_url_t != get_post_meta( $translated_object_id, '_menu_item_url', true );
$translated_object_title = $icl_st_label_exists ? $translated_object_title_t : $translated_object_title;
$translated_object_url = $icl_st_url_exists ? $translated_object_url_t : $translated_object_url;
}
return array(
$translated_object_url,
$translated_object_title,
$url_changed,
$label_changed,
);
}
/**
* @param array<string,int> $item_translations
* @param int $menu_id
*/
private function fix_assignment_to_menu( $item_translations, $menu_id ) {
foreach ( $item_translations as $lang_code => $item_id ) {
$correct_menu_id = $this->term_translations->term_id_in( $menu_id, $lang_code );
if ( $correct_menu_id ) {
$ttid_trans = $this->wpdb->get_var(
$this->wpdb->prepare(
" SELECT tt.term_taxonomy_id
FROM {$this->wpdb->term_taxonomy} tt
LEFT JOIN {$this->wpdb->term_relationships} tr
ON tt.term_taxonomy_id = tr.term_taxonomy_id
AND tr.object_id = %d
WHERE tt.taxonomy = 'nav_menu'
AND tt.term_id = %d
AND tr.term_taxonomy_id IS NULL
LIMIT 1",
$item_id,
$correct_menu_id
)
);
if ( $ttid_trans ) {
$this->wpdb->insert(
$this->wpdb->term_relationships,
array(
'object_id' => $item_id,
'term_taxonomy_id' => $ttid_trans,
)
);
}
}
}
}
/**
* Removes potentially mis-assigned menu items from their menu, whose language differs from that of their
* associated menu.
*/
private function fix_language_conflicts() {
$wrong_items = $this->wpdb->get_results(
" SELECT r.object_id, t.term_taxonomy_id
FROM {$this->wpdb->term_relationships} r
JOIN {$this->wpdb->prefix}icl_translations ip
JOIN {$this->wpdb->posts} p
ON ip.element_type = CONCAT('post_', p.post_type)
AND ip.element_id = p.ID
AND ip.element_id = r.object_id
JOIN {$this->wpdb->prefix}icl_translations it
JOIN {$this->wpdb->term_taxonomy} t
ON it.element_type = CONCAT('tax_', t.taxonomy)
AND it.element_id = t.term_taxonomy_id
AND it.element_id = r.term_taxonomy_id
WHERE p.post_type = 'nav_menu_item'
AND t.taxonomy = 'nav_menu'
AND ip.language_code != it.language_code"
);
foreach ( $wrong_items as $item ) {
$this->wpdb->delete(
$this->wpdb->term_relationships,
array(
'object_id' => $item->object_id,
'term_taxonomy_id' => $item->term_taxonomy_id,
)
);
}
}
}