- Stato
- Aperto
- Tipo
- Bug
- Plugin/tema correlato
- WPBakery
- Tag dell’argomento
- Compatibility
Panoramica del problema
Un’Immagine singola o una Galleria di immagini che ha un link personalizzato perde la sua immagine nella pagina tradotta. La pagina originale è corretta.
WPBakery 9.0 ha modificato il modo in cui salva un’immagine che ha un link. WPML non riconosce il nuovo valore, quindi la pagina tradotta non mostra alcuna immagine. Inoltre, il link non è più traducibile e la pagina tradotta mantiene il link della pagina originale.
Anche le pagine create con versioni precedenti sono interessate. WPBakery converte l’elemento quando lo apri nell’editor e lo salvi. Una pagina che oggi funziona può presentare questo problema dopo averla modificata.
Questo problema riguarda WPBakery Page Builder 9.0 e versioni successive, con WPML 4.9 e WPML 5.0.
Soluzione alternativa
Fai un backup completo del tuo sito prima di iniziare.
- Crea la cartella
wp-content/mu-plugins/se non è presente nel tuo sito. - Crea il file
wp-content/mu-plugins/wpml-wpbakery-9-linked-images.php. È un Must-Use Plugin (mu-plugin). Si carica automaticamente e non c’è nulla da attivare. Aggiungi questo contenuto:
Mostra il codice
<?php
/**
* Plugin Name: WPML fix - WPBakery 9 images with a link
* Description: Keeps images that have a custom link visible on translated pages, and makes the link translatable.
* Version: 1.1
*
* WPBakery 9.0 saves the custom link inside the same attribute as the image ID.
* This file teaches WPML to read that value. It does two things:
*
* 1. Translates the image IDs, so the image appears on the translated page.
* 2. Registers each link URL for translation, so a translated page can point
* to a translated target.
*
* It covers the Single Image and Image Gallery elements. Remove this file after
* you update to the WPML version that fixes this issue.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/* -------------------------------------------------------------------------
* Part 1 - the image IDs
* ---------------------------------------------------------------------- */
/**
* The WPBakery elements that changed in 9.0, and the attribute that holds the image IDs.
*/
function wpml_wpb9_attributes() {
return [
'vc_single_image' => 'image',
'vc_gallery' => 'images',
];
}
/**
* Step 1. Stop WPML from rewriting these two attributes.
*/
add_filter(
'option_wpml_pb_media_shortcode',
function ( $shortcodes ) {
if ( ! is_array( $shortcodes ) ) {
return $shortcodes;
}
$attributes = wpml_wpb9_attributes();
foreach ( $shortcodes as $index => $shortcode ) {
$tag = isset( $shortcode['tag']['name'] ) ? $shortcode['tag']['name'] : '';
if ( isset( $attributes[ $tag ], $shortcode['attributes'][ $attributes[ $tag ] ] ) ) {
unset( $shortcodes[ $index ]['attributes'][ $attributes[ $tag ] ] );
}
}
return $shortcodes;
}
);
/**
* Step 2. Translate the image IDs, in both storage formats.
*/
class WPML_WPB9_Linked_Images {
private $lang;
public function translate( $post ) {
$details = apply_filters( 'wpml_post_language_details', null, $post->ID );
if ( ! isset( $details['language_code'] ) ) {
return;
}
$this->lang = $details['language_code'];
$content = $post->post_content;
foreach ( wpml_wpb9_attributes() as $tag => $attribute ) {
if ( false === strpos( $content, '[' . $tag ) ) {
continue;
}
$pattern = '/(\[' . preg_quote( $tag, '/' ) . '(?: [^\]]* | )' . preg_quote( $attribute, '/' ) . '=(?:"|\'))([^"\']*)/';
$content = preg_replace_callback( $pattern, [ $this, 'replace' ], $content );
}
if ( $content === $post->post_content ) {
return;
}
$post->post_content = $content;
kses_remove_filters();
wpml_update_escaped_post(
[
'ID' => $post->ID,
'post_content' => $content,
'tags_input' => wp_get_post_tags( $post->ID, [ 'fields' => 'ids' ] ),
],
$this->lang
);
kses_init();
}
private function replace( array $matches ) {
$value = $matches[2];
// WPBakery 9.0 and later: a JSON map of image ID to link data.
if ( '{' === substr( ltrim( $value ), 0, 1 ) ) {
$data = json_decode( str_replace( [ '`{`', '`}`', '``' ], [ '[', ']', '"' ], $value ), true );
if ( ! is_array( $data ) ) {
return $matches[0];
}
$translated = [];
foreach ( $data as $id => $link ) {
$new_id = (string) $this->translate_id( $id );
if ( ! isset( $translated[ $new_id ] ) ) {
$translated[ $new_id ] = $link;
}
}
$json = (string) wp_json_encode( (object) $translated, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
return $matches[1] . str_replace( [ '"', '[', ']' ], [ '``', '`{`', '`}`' ], $json );
}
// WPBakery 8.x and earlier: image IDs separated by commas.
if ( ! preg_match( '/^[\d\s,]*$/', $value ) ) {
return $matches[0];
}
$ids = array_map( [ $this, 'translate_id' ], explode( ',', $value ) );
return $matches[1] . implode( ',', $ids );
}
private function translate_id( $id ) {
$id = (int) $id;
if ( $id < 1 ) {
return $id;
}
$translated = apply_filters( 'wpml_object_id', $id, 'attachment', true, $this->lang );
return $translated ? (int) $translated : $id;
}
/**
* WPML calls these two while it collects the media a page uses.
* This file only translates the IDs, so there is nothing to collect.
*/
public function find_media( $post ) {
}
public function get_media() {
return [];
}
}
add_filter(
'wpml_pb_get_media_updaters',
function ( $updaters ) {
$updaters['wpml_wpb9_linked_images'] = new WPML_WPB9_Linked_Images();
return $updaters;
},
PHP_INT_MAX
);
/* -------------------------------------------------------------------------
* Part 2 - the custom link URLs
* ---------------------------------------------------------------------- */
const WPML_WPB9_ENCODING = 'wpb9_image_links';
/** Which keys inside each link object a translator may edit. */
function wpml_wpb9_translatable_keys() {
return [ 'url', 'title' ];
}
/** All keys we keep, in WPBakery's own order. */
function wpml_wpb9_link_keys() {
return [ 'url', 'title', 'target', 'rel' ];
}
/**
* Step 1. Declare the attribute translatable, with our encoding.
*
* This hooks `wpml_config_array`. WPML rebuilds its shortcode settings from the
* plugin configuration, so the attribute has to be added there. A change made
* anywhere else is discarded when WPML reads the configuration again.
*
* The entry carries only an encoding, not a type. WPML does not register a
* string for an attribute that already has the `media-ids` type, so the links
* need their own entry.
*/
add_filter(
'wpml_config_array',
function ( $config ) {
$targets = wpml_wpb9_attributes();
if ( ! isset( $config['wpml-config']['shortcodes']['shortcode'] ) ) {
return $config;
}
$shortcodes = &$config['wpml-config']['shortcodes']['shortcode'];
foreach ( $shortcodes as &$shortcode ) {
$tag = isset( $shortcode['tag']['value'] ) ? $shortcode['tag']['value'] : '';
if ( ! isset( $targets[ $tag ], $shortcode['attributes']['attribute'] ) ) {
continue;
}
$attribute = $targets[ $tag ];
$list = $shortcode['attributes']['attribute'];
if ( ! is_array( $list ) || ! is_numeric( key( $list ) ) ) {
$list = [ $list ];
}
foreach ( $list as $existing ) {
$encoding = isset( $existing['attr']['encoding'] ) ? $existing['attr']['encoding'] : '';
if ( ( $existing['value'] ?? '' ) === $attribute && WPML_WPB9_ENCODING === $encoding ) {
continue 2;
}
}
$list[] = [
'value' => $attribute,
'attr' => [ 'encoding' => WPML_WPB9_ENCODING ],
];
$shortcode['attributes']['attribute'] = $list;
}
unset( $shortcode );
return $config;
},
9
);
/**
* Step 2. Decode: one translatable string per URL.
*/
add_filter(
'wpml_pb_shortcode_decode',
function ( $content, $encoding, $encoded_content = '' ) {
if ( WPML_WPB9_ENCODING !== $encoding ) {
return $content;
}
$value = is_string( $encoded_content ) && '' !== $encoded_content ? $encoded_content : $content;
if ( ! is_string( $value ) || '{' !== substr( ltrim( $value ), 0, 1 ) ) {
return [];
}
$data = json_decode( str_replace( [ '`{`', '`}`', '``' ], [ '[', ']', '"' ], $value ), true );
if ( ! is_array( $data ) ) {
return [];
}
$translatable = wpml_wpb9_translatable_keys();
$out = [];
foreach ( $data as $id => $link ) {
if ( ! is_array( $link ) ) {
continue;
}
foreach ( wpml_wpb9_link_keys() as $key ) {
$out[ $key . '_' . $id ] = [
'value' => isset( $link[ $key ] ) ? (string) $link[ $key ] : '',
'translate' => in_array( $key, $translatable, true ),
];
}
}
return $out;
},
10,
3
);
/**
* Step 3. Encode: rebuild the JSON with the translated URLs, same image IDs.
*/
add_filter(
'wpml_pb_shortcode_encode',
function ( $content, $encoding, $decoded_content = null ) {
if ( WPML_WPB9_ENCODING !== $encoding ) {
return $content;
}
$source = is_array( $content ) ? $content : (array) $decoded_content;
if ( ! $source ) {
return $content;
}
$map = [];
foreach ( $source as $combined_key => $value ) {
$parts = explode( '_', $combined_key );
$id = array_pop( $parts );
$key = implode( '_', $parts );
if ( '' === $id || '' === $key ) {
continue;
}
$map[ $id ][ $key ] = is_array( $value ) && isset( $value['value'] ) ? $value['value'] : $value;
}
if ( ! $map ) {
return $content;
}
$out = [];
foreach ( $map as $id => $link ) {
$entry = [];
foreach ( wpml_wpb9_link_keys() as $key ) {
$entry[ $key ] = isset( $link[ $key ] ) ? (string) $link[ $key ] : '';
}
$out[ (string) $id ] = $entry;
}
$json = (string) wp_json_encode( (object) $out, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
return str_replace( [ '"', '[', ']' ], [ '``', '`{`', '`}`' ], $json );
},
10,
3
);
- Apri ogni pagina che utilizza un’Immagine singola o una Galleria immagini con un link personalizzato. Salva di nuovo la pagina. Questo registra il link per la traduzione.
- Traduci il link in WPML → Traduzione stringhe, oppure nell’editor di traduzione insieme al resto della pagina.
- Apri ogni pagina tradotta che ha perso l’immagine e salvala di nuovo. Questo ripristina l’immagine.
Cosa aspettarsi
L’immagine appare di nuovo sulla pagina tradotta. Il link personalizzato diventa traducibile, così una pagina tradotta può puntare a una destinazione tradotta.
La soluzione alternativa copre gli elementi Immagine singola e Galleria immagini. Questi sono i due elementi che memorizzano un link in questo modo in WPBakery 9.0.
Una pagina tradotta come duplicato mantiene i link della pagina originale. WPML copia un duplicato dal suo originale, quindi la traduzione dei link non si applica ad esso.
Rimuovi il file dopo aver aggiornato alla versione di WPML che risolve questo problema.