WordPress удаляет экранирующие обратные слеши из JSON строк в post_meta
Я думал, что облегчаю себе жизнь и думаю о будущем, сохраняя некоторый контент в виде JSON в пользовательских полях post_meta. К сожалению, WordPress не согласен и делает мою жизнь невероятно сложной.
У меня есть JSON строка, которая выглядит примерно так. Это всего лишь часть, а строка комментария - просто пример юникодных символов. Вся строка генерируется с помощью json_encode.
{
"0": {
"name": "Chris",
"url": "testdomain.com",
"comment": "\u00a5 \u00b7 \u00a3 \u00b7 \u20ac \u00b7 \u00b7 \u00a2 \u00b7 \u20a1 \u00b7 \u20a2 \u00b7 \u20a3 \u00b7 \u20a4 \u00b7 \u20a5 \u00b7 \u20a6 \u00b7 \u20a7 \u00b7 \u20a8 \u00b7 \u20a9 \u00b7 \u20aa \u00b7 \u20ab \u00b7 \u20ad \u00b7 \u20ae \u00b7 \u20af \u00b7 \u20b9"
}
}
К сожалению, после сохранения с помощью update_post_meta
, она выглядит так:
{
"0": {
"name": "Chris",
"url": "testdomain.com",
"comment": "u00a5 u00b7 u00a3 u00b7 u20ac u00b7 u00b7 u00a2 u00b7 u20a1 u00b7 u20a2 u00b7 u20a3 u00b7 u20a4 u00b7 u20a5 u00b7 u20a6 u00b7 u20a7 u00b7 u20a8 u00b7 u20a9 u00b7 u20aa u00b7 u20ab u00b7 u20ad u00b7 u20ae u00b7 u20af u00b7 u20b9"
}
}
И со снятыми слешами, её нельзя преобразовать обратно в полезный контент с помощью json_decode
.
Есть идеи, почему WordPress так делает, и можно ли этого избежать? Я не могу использовать флаг JSON_UNESCAPED_UNICODE, потому что у меня установлен PHP 5.3.x, и я уже пробовал кодировать с помощью htmlentities
перед передачей в json_encode
, но это захватывает только небольшую часть UTF-8 символов.
Заранее спасибо!
(ПРИМЕЧАНИЕ: Для справки, я знаю, что можно просто сохранить массив напрямую в post_meta, и он будет сериализован, и произойдет волшебство, но мне просто нравится идея хранить данные в формате JSON. Если нет простого и элегантного решения, я сдамся, но я очень надеюсь, что такое решение есть!)

Есть изящный способ решить эту проблему!
Пропустите строку в формате JSON через функцию wp_slash()
. Эта функция экранирует начальный слеш у каждого закодированного Unicode-символа, что предотвратит их удаление функцией update_metadata()
.

Это обходное решение для серьёзной ошибки в Wordpress. Огромное спасибо!

Этот ответ должен быть принятым. У меня были проблемы с контентом, импортируемым из GitHub через wp_insert_post, где это было основной проблемой - удаление слешей в примерах кода. Пропуск строки через wp_slash перед передачей в wp_insert_post решил проблему. Спасибо!

Это решение остаётся полезным даже сегодня. Я потратил часы на поиск обходного пути без единой зацепки, пока не нашёл это. Если вы хотите добавить этот ответ на мой вопрос здесь: https://stackoverflow.com/questions/61091853/wordpress-breaking-unicodes-when-saving-data, я отмечу его как правильный ответ. Огромное вам спасибо!

Кто бы мог подумать, что 8-летний пост сможет мне помочь. У меня были проблемы с сохранением сериализованных (Gutenberg) блоков, где JSON-закодированные атрибуты теряли свои слеши. Пропуск вывода serialize_block через wp_slash решил проблему.

Похоже, этого никак не избежать.
Функция update_metadata(), которая в конечном итоге отвечает за сохранение метаданных, явно применяет stripslashes_deep() к значению метаполя. Эта функция удаляет слеши даже из элементов массива, если значение является массивом.
Существует фильтр sanitize_meta, который срабатывает ПОСЛЕ этого, и в который можно добавить свой обработчик. Но к этому моменту ваши слеши уже удалены, поэтому невозможно достоверно определить, где их нужно было оставить (или, по крайней мере, я не знаю, как отличить цитирование легитимных JSON-разделителей от частей значений).
Не могу сказать, почему так сделано, но факт остается фактом. Вероятно, потому что в конечном итоге данные проходят через wpdb->update, которому нужны строки без экранирования.
Как вы и опасались, вероятно, лучше просто сохранять значение как массив, который будет сериализован (как вы и говорили). Если позже вам понадобится JSON, вы всегда можете пропустить его через json_encode().

Я этого и боялся, но хорошо знать причину происходящего. Большое спасибо за быстрый ответ!

@jave.web Действительно, нельзя избежать того, что update_metadata() удаляет слеши из строки. Другие ответы предлагают (очень умные) обходные пути, которые по сути "двойное экранирование" строки, чтобы неизбежное удаление слешей убрало только дополнительные слеши, оставив оригинальные. Лично я считаю, что "элегантный" способ решения - просто хранить данные в массиве, который не требует специальной обработки или предварительного форматирования. Затем конвертировать в json при необходимости. Но это всего лишь моё предпочтение.

Эта функция выполняет преобразование с помощью preg_replace:
function preg_replace_add_slash_json($value) {
return preg_replace('/(u[0-9a-fA-F]{4})/i', '\\\$1', $value);
}
Перед каждой последовательностью "uXXXX" (X=0..F, шестнадцатеричный) она добавляет обратный слеш. Вызывайте эту функцию перед сохранением в базу данных.

Для тех, кто всё ещё испытывает трудности с сохранением JSON-закодированной Unicode-строки через wp_update_post, ниже приведено решение, которое сработало у меня. Найдено в class-wp-rest-posts-controller.php
// преобразуем объект записи в массив, иначе wp_update_post ожидает неэкранированные данные
wp_update_post( wp_slash( (array) $my_post ) );
Вот пример:
$objectToEncodeToJson = array(
'my_custom_key' => '<div>Здесь HTML, который будет преобразован в Unicode в базе данных.</div>'
);
$postContent = json_encode($objectToEncodeToJson,JSON_HEX_TAG|JSON_HEX_QUOT);
$my_post = array(
'ID' => $yourPostId,
'post_content' => $postContent
);
wp_update_post( wp_slash( (array) $my_post ) );

Интересный способ обойти это - кодирование в base64, см. пример ниже.
$data = Array(0 => array('name' => 'chris', 'URL' => "hello.com"));
$to_json = json_encode($data);
echo $to_json . "<br />";
// выводит [{"name":"chris","URL":"hello.com"}]
$to_base64 = base64_encode($to_json);
Echo $to_base64 . "<br />";
// выводит W3sibmFtZSI6ImNocmlzIiwiVVJMIjoiaGVsbG8uY29tIn1d
$back_to_json = base64_decode($to_base64);
Echo $back_to_json . "<br />";
// выводит [{"name":"chris","URL":"hello.com"}]
$back_to_aray = json_decode($back_to_json);
print_r($back_to_aray);
// выводит Array ( [0] => stdClass Object ( [name] => chris [URL] => hello.com ))

Я знаю, что это старый вопрос, но он до сих пор актуален для разработчиков. В качестве справочного материала привожу полезную ветку обсуждения из трекера проблем ядра WordPress, которая показалась мне информативной: https://core.trac.wordpress.org/ticket/21767

Вы можете использовать функцию WordPress stripslashes_deep().
<?php stripslashes_deep($your_json);?>
Для справки посетите эту страницу
