- Status
- Offen
- Typ
- Bug
- Zugehöriges Plugin/Theme
- WPBakery
- Themen-Tags
- Compatibility
Übersicht über das Problem
Ein Single Image oder eine Image Gallery mit einem benutzerdefinierten Link verliert das Bild auf der übersetzten Seite. Die Originalseite ist korrekt.
WPBakery 9.0 hat geändert, wie ein Bild mit einem Link gespeichert wird. WPML erkennt den neuen Wert nicht, sodass die übersetzte Seite kein Bild anzeigt. Der Link ist zudem nicht mehr übersetzbar, und die übersetzte Seite behält den Link der Originalseite.
Seiten, die mit früheren Versionen erstellt wurden, sind ebenfalls betroffen. WPBakery konvertiert das Element, wenn Sie es im Editor öffnen und speichern. Eine Seite, die heute funktioniert, kann dieses Problem aufweisen, nachdem Sie sie bearbeiten.
Dies betrifft WPBakery Page Builder 9.0 und neuer mit WPML 4.9 und WPML 5.0.
Problemumgehung
Erstellen Sie ein vollständiges Backup Ihrer Website, bevor Sie beginnen.
- Erstellen Sie den Ordner
wp-content/mu-plugins/, falls er auf Ihrer Website nicht vorhanden ist. - Erstellen Sie die Datei
wp-content/mu-plugins/wpml-wpbakery-9-linked-images.php. Es handelt sich um ein Must-Use-Plugin (mu-plugin). Es wird automatisch geladen und es muss nichts aktiviert werden. Fügen Sie diesen Inhalt hinzu:
Code anzeigen
<?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
);
- Öffnen Sie jede Seite, die ein Single Image oder eine Image Gallery mit einem benutzerdefinierten Link verwendet. Speichern Sie die Seite erneut. Dadurch wird der Link für die Übersetzung registriert.
- Übersetzen Sie den Link unter WPML → String-Übersetzung oder zusammen mit dem Rest der Seite im Übersetzungs-Editor.
- Öffnen Sie jede übersetzte Seite, die ihr Bild verloren hat, und speichern Sie sie erneut. Dadurch wird das Bild wieder angezeigt.
Was Sie erwartet
Das Bild erscheint wieder auf der übersetzten Seite. Der benutzerdefinierte Link wird übersetzbar, sodass eine übersetzte Seite auf ein übersetztes Ziel verweisen kann.
Die Problemumgehung gilt für die Elemente Single Image und Image Gallery. Dies sind die beiden Elemente, die einen Link in WPBakery 9.0 auf diese Weise speichern.
Eine als Duplikat übersetzte Seite behält die Links der Originalseite. WPML kopiert ein Duplikat vom Original, daher gilt die Link-Übersetzung dafür nicht.
Entfernen Sie die Datei, nachdem Sie das Update auf die WPML-Version installiert haben, die dieses Problem behebt.