D
P
0

WordPress & Elementor

Regex Search-Replace di `_elementor_data` Bikin Text Widget Tampil Lorem Ipsum? JSON Korup, Elementor Fallback ke Placeholder

2 Juli 2026·5 menit baca
Regex Search-Replace di `_elementor_data` Bikin Text Widget Tampil Lorem Ipsum? JSON Korup, Elementor Fallback ke Placeholder

Sebuah situs perusahaan yang dibangun dengan Elementor butuh transformasi konten massal: markup di dalam text widget harus dirapikan di puluhan halaman sekaligus, salah satunya menurunkan heading yang liar supaya konsisten. Saya tahu Elementor menyimpan seluruh setting widget dalam satu post meta bernama _elementor_data: satu string JSON raksasa berisi semua section, column, dan widget, termasuk HTML rich-text di field editor milik setiap text widget. Mengedit puluhan halaman lewat editor satu per satu jelas bukan opsi. Jadi saya ambil jalan tercepat yang terpikir oleh developer yang sedang buru-buru: regex langsung di string mentahnya.

// The shortcut that caused all of this. Do not do this.
$raw = get_post_meta( $post_id, '_elementor_data', true );
$raw = preg_replace( '/<h3([^>]*)>(.*?)<\/h3>/s', '<h2$1>$2</h2>', $raw );
update_post_meta( $post_id, '_elementor_data', $raw );

Di sebagian besar halaman hasilnya kelihatan beres. Lalu saya buka beberapa halaman lain dan langsung dingin: text widget-nya menampilkan Lorem ipsum dolor sit amet. Bukan satu widget, beberapa sekaligus. Konten klien lenyap, digantikan teks latin bawaan. Refleks pertama saya menyalahkan replacement: mungkin regex saya "mengganti" konten dengan sesuatu yang salah. Tapi itu tidak masuk akal, tidak ada kata lorem di pattern maupun replacement saya.

Saya cek database. Meta _elementor_data di halaman yang rusak masih ada dan masih panjang. Tapi begitu saya lempar ke json_decode, hasilnya null. JSON-nya sudah tidak valid. Di titik itu gambarnya mulai jelas: regex saya tidak mengganti konten dengan lorem ipsum. Regex saya menghancurkan strukturnya, dan lorem ipsum datang dari tempat lain.

Kenapa ini terjadi

_elementor_data bukan HTML. Dia JSON, dan HTML konten hidup di dalam string JSON. Bentuk tersimpannya kira-kira begini:

{"id":"3a91f","widgetType":"text-editor","settings":{"editor":"<h3 class=\"intro\">About the studio<\/h3><p>We opened in 2012.<\/p>"}}

Perhatikan detailnya: tanda kutip di-escape jadi \", garis miring jadi \/, dan kadang ada escape unicode seperti \u00a0 yang menyelinap dari editor. Pattern saya ditulis sambil membayangkan HTML hasil render. Di bentuk tersimpan, tag penutup bukan </h3> melainkan <\/h3>, jadi bagian (.*?) yang saya beri flag /s terus melahap karakter sampai menemukan kecocokan literal di tempat lain, jauh melewati batas string-nya sendiri. Dalam perjalanannya dia menelan tanda kutip escaped, koma, dan kurung kurawal milik struktur JSON. Pattern yang kelihatan benar akhirnya memakan delimiter. Di beberapa halaman kebetulan aman; di halaman lain, satu match nakal cukup untuk membuat value editor jadi invalid dan seluruh dokumen gagal di-parse.

Lalu dari mana lorem ipsum-nya? Itu perilaku Elementor sendiri. Ketika Elementor memuat text widget yang setting editor-nya kosong atau tidak valid, dia jatuh ke konten default widget itu, dan konten default text widget adalah paragraf Lorem ipsum. Jadi konten klien tidak "diganti lorem ipsum". Konten itu hancur, dan Elementor menambal lubangnya dengan placeholder bawaan. Kalau kamu melihat lorem ipsum muncul di situs yang sudah live, itu bukan bug tampilan: nilai tersimpanmu sudah rusak.

Perbaikannya

Halaman yang korup saya pulihkan dari backup database. Setelah itu saya tulis ulang transformasinya dengan aturan yang seharusnya saya pegang dari awal: jangan pernah regex _elementor_data. Decode dulu, jalan-jalan di strukturnya, transformasi di nilai yang sudah bersih, lalu encode balik.

function transform_editor_html( $html ) {
    // Work on the decoded value: this is real HTML now, not JSON-escaped soup.
    $html = str_replace( '<h3', '<h2', $html );
    $html = str_replace( '</h3>', '</h2>', $html );
    return $html;
}
 
function walk_elements( array $elements, callable $transform ) {
    foreach ( $elements as &$element ) {
        if ( ( $element['widgetType'] ?? '' ) === 'text-editor' && ! empty( $element['settings']['editor'] ) ) {
            $element['settings']['editor'] = $transform( $element['settings']['editor'] );
        }
        if ( ! empty( $element['elements'] ) ) {
            $element['elements'] = walk_elements( $element['elements'], $transform );
        }
    }
    return $elements;
}

Widget yang jadi target dikenali lewat widgetType === 'text-editor', dan karena section serta column bisa bersarang, walker-nya harus rekursif. String editor penuh saya bangun ulang di PHP, pada nilai yang sudah di-decode, bukan ditambal di tengah JSON mentah. Lalu simpan balik:

$raw  = get_post_meta( $post_id, '_elementor_data', true );
$data = json_decode( $raw, true );
 
if ( ! is_array( $data ) ) {
    return; // Never write back something you could not parse.
}
 
// Keep an escape hatch: copy the untouched value to a backup key first.
update_post_meta( $post_id, '_elementor_data_backup', wp_slash( $raw ) );
 
$data = walk_elements( $data, 'transform_editor_html' );
 
// Elementor stores this meta slashed, so wp_slash() is mandatory here.
update_post_meta( $post_id, '_elementor_data', wp_slash( wp_json_encode( $data ) ) );

Dua jebakan di langkah simpan. Pertama, wp_slash() itu wajib: Elementor menyimpan meta ini dalam bentuk slashed, dan update_post_meta melakukan unslash sekali saat menulis. Tanpa wp_slash(), escape di dalam JSON ikut termakan, dan kamu merusak data dari arah yang berbeda. Kedua, setelah data berubah, CSS hasil generate Elementor bisa basi, jadi regenerate:

if ( class_exists( '\Elementor\Plugin' ) ) {
    \Elementor\Plugin::$instance->files_manager->clear_cache();
}

Versi baru ini saya jalankan dry-run dulu di satu post ID, saya cek hasilnya di editor Elementor dan di frontend, baru setelah itu dilepas ke seluruh situs. Kali ini tidak ada satu pun lorem ipsum.

Checklist sebelum menyentuh _elementor_data

  • Data terstruktur pakai alat terstruktur: json_decode, walk, wp_json_encode. Jangan pernah regex JSON di dalam string.
  • Lorem ipsum muncul di text widget berarti nilai tersimpannya invalid, bukan bug tampilan.
  • Salin _elementor_data ke meta key cadangan sebelum transformasi massal.
  • Uji di satu post ID, verifikasi di editor dan frontend, baru jalankan site-wide.
  • wp_slash() saat simpan, lalu clear cache CSS Elementor.

Pelajaran

Regex terasa seperti jalan pintas justru karena dia mengabaikan struktur, dan itu persis alasan dia berbahaya di sini. JSON di dalam string, dengan kutip escaped, garis miring escaped, dan HTML bersarang, akan selalu punya satu kasus yang membuat pattern paling rapi pun memakan delimiter. Sejak insiden ini, setiap kali tergoda meng-regex data yang punya struktur, saya berhenti dan bertanya dulu: berapa lama waktu yang saya hemat sekarang, dibanding waktu yang saya habiskan memulihkan konten klien dari backup?