D
P
0

WordPress & PHP

strtoupper Mengubah Chip Kategori WordPress Jadi 'BANKING & NEOBANKS'

16 Juni 2026·3 menit baca
strtoupper Mengubah Chip Kategori WordPress Jadi 'BANKING & NEOBANKS'

Di sebuah situs WordPress yang saya rawat, ada chip kategori yang seharusnya menampilkan "BANKING & NEOBANKS". Yang muncul di layar malah ini:

BANKING & NEOBANKS

Teks & literal, di tengah-tengah label. Bukan render karakter &, tapi benar-benar lima karakter: ampersand, A, M, P, titik koma. Kelihatannya seperti kerusakan encoding acak, padahal sebenarnya sangat logis begitu Anda tahu penyebabnya.

Markup-nya sederhana — sebuah chip yang meng-uppercase nama term:

<span class="chip">
    <?php echo esc_html( strtoupper( $term->name ) ); ?>
</span>

Nama kategorinya "Banking & Neobanks". Jadi kenapa & malah jadi &AMP;?

Akar masalahnya: nama term sudah ter-encode di konteks display

Inilah bagian yang membuat saya terjebak. Ketika WordPress menyerahkan field term ke Anda di konteks 'display' (yang terjadi pada get_the_terms, wp_get_post_terms, dan saat term diakses lewat loop biasa), ia melewatkan field itu melalui sanitize_term_field. Dan di konteks 'display', sanitizer itu meng-encode & menjadi &amp;.

Artinya $term->name untuk "Banking & Neobanks" sebenarnya bukan string yang Anda kira. Nilai aslinya di memori adalah:

Banking &amp; Neobanks

Sekarang ikuti alurnya. Kalau Anda hanya esc_html lalu cetak, semuanya baik-baik saja — browser menerima &amp; dan men-decode-nya kembali jadi &. Itulah kenapa kode yang tampak naif sering kali "kebetulan benar".

Tapi begitu Anda menyisipkan strtoupper, kode itu meng-uppercase seluruh string — termasuk entity-nya:

Banking &amp; Neobanks
   strtoupper ↓
BANKING &AMP; NEOBANKS

Dan &AMP; bukan HTML entity yang valid. Entity bersifat case-sensitive; yang valid adalah &amp; (huruf kecil). Karena &AMP; tidak dikenali, browser tidak men-decode-nya — ia menampilkannya apa adanya. Itulah sebabnya teks literal muncul di chip.

Kenapa CSS text-transform tidak kena, tapi PHP/JS kena

Saya sempat berpikir untuk uppercase lewat CSS saja, dan itu memang menghindari bug ini — karena alasan yang penting untuk dipahami:

.chip { text-transform: uppercase; } /* aman */

text-transform: uppercase bekerja pada teks yang sudah dirender. Pada saat CSS bekerja, browser sudah men-decode &amp; menjadi &, jadi yang di-uppercase adalah karakter & — bukan entity. Hasilnya visual saja, dan benar.

Tapi strtoupper (PHP) dan toUpperCase() (JS) bekerja pada string mentah sebelum browser sempat men-decode apa pun. Mereka melihat &amp; dan dengan patuh meng-uppercase-nya jadi &AMP;. Inilah jebakannya: fungsi case di lapisan kode merusak entity, fungsi case di lapisan tampilan tidak.

Perbaikannya: decode entity SEBELUM transformasi case

Solusinya adalah men-decode entity kembali ke karakter mentahnya dulu, baru meng-uppercase:

<span class="chip">
    <?php echo esc_html(
        strtoupper( wp_specialchars_decode( $term->name, ENT_QUOTES ) )
    ); ?>
</span>

Sekarang alurnya benar:

  1. wp_specialchars_decode( $term->name, ENT_QUOTES ) mengubah Banking &amp; NeobanksBanking & Neobanks
  2. strtoupper( ... ) mengubahnya → BANKING & NEOBANKS
  3. esc_html( ... ) meng-encode ulang &&amp; dengan aman untuk output
  4. Browser men-decode &amp;& saat render

Hasil akhir di layar: BANKING & NEOBANKS. Persis seperti yang diinginkan.

Jebakan yang sama berlaku untuk strtolower, ucfirst, dan mb_strtoupper. Apa pun fungsi case di PHP — atau toUpperCase/toLowerCase di JavaScript — akan merusak entity kalau dijalankan di atas string term mentah.

Pelajaran

Nama term di WordPress datang dalam keadaan ter-encode sebagai HTML entity saat konteks 'display'. $term->name untuk "Banking & Neobanks" sebenarnya adalah "Banking & Neobanks". esc_html saja tidak masalah karena browser men-decode entity kembali — tapi begitu Anda menjalankan strtoupper/strtolower di atasnya, &amp; berubah jadi &AMP;/&amp; yang tidak valid dan bocor sebagai teks literal.

Aturannya: jangan pernah jalankan fungsi case PHP/JS di atas nama term tanpa wp_specialchars_decode( ..., ENT_QUOTES ) lebih dulu. Kalau Anda hanya butuh tampilan uppercase, text-transform: uppercase di CSS adalah jalan teraman — itu bekerja pada teks yang sudah ter-decode dan tidak akan pernah menyentuh entity.