D
P
0

WordPress & PHP

Halaman Kategori Menampilkan `00 stories` Padahal Post-nya Ada? `meta_query` Warisan Seeder Membuang Semua Konten Asli

1 Juli 2026·4 menit baca
Halaman Kategori Menampilkan `00 stories` Padahal Post-nya Ada? `meta_query` Warisan Seeder Membuang Semua Konten Asli

Sebuah situs berita klien mendarat di meja saya dengan laporan yang aneh: beberapa halaman kategori menampilkan "00 stories" di header dan grid artikel di bawahnya kosong melompong. Aneh, karena begitu saya buka wp-admin, kategori-kategori itu jelas punya artikel published. Belasan. Kontennya ada, tapi halamannya bersikeras bilang nol.

Kecurigaan pertama saya jatuh ke counter-nya. Ternyata dia jujur. Angka "00" itu cuma found_posts dari query utama yang diformat dengan leading zero. Query-nya betulan mengembalikan nol baris. Jadi ini bukan bug tampilan; ada sesuatu di antara "post published yang ter-assign ke kategori" dan "query halaman kategori" yang membuang semuanya.

Dump SQL-nya, jangan tatap template

Kalau sebuah listing kosong padahal kontennya ada, godaan terbesar adalah membongkar template. Itu buang-buang waktu. WP_Query menyimpan SQL final yang dia jalankan di properti $query->request, dan itu tempat pertama yang harus dilihat:

add_action( 'wp', function () {
    if ( is_category() ) {
        global $wp_query;
        error_log( $wp_query->request );
    }
} );

Hasilnya kira-kira seperti ini (disederhanakan):

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships
    ON ( wp_posts.ID = wp_term_relationships.object_id )
INNER JOIN wp_postmeta
    ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1
    AND wp_term_relationships.term_taxonomy_id IN (12)
    AND wp_postmeta.meta_key = '_demo_source_id'
    AND wp_posts.post_type = 'post'
    AND wp_posts.post_status = 'publish'
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 12

Dua baris langsung mencurigakan: INNER JOIN wp_postmeta dan syarat meta_key = '_demo_source_id'. Saya tidak pernah menulis meta key itu. Tidak ada plugin yang saya kenal yang memakainya. Sekali grep di theme, ketemu pelakunya:

add_action( 'pre_get_posts', function ( $query ) {
    if ( $query->is_category() && $query->is_main_query() ) {
        $query->set( 'meta_query', array(
            array(
                'key'     => '_demo_source_id',
                'compare' => 'EXISTS',
            ),
        ) );
    }
} );

Akar masalah: tema yang dibesarkan oleh data demo

Theme situs ini awalnya dibangun di atas konten hasil seeder. Seeder itu menandai setiap post demo dengan meta key _demo_source_id. Di suatu titik, mungkin untuk memfilter konten demo, seseorang menambahkan meta_query ke SETIAP query kategori yang mensyaratkan key itu EXISTS.

Selama development, semuanya terlihat normal, karena semua post memang hasil seeder dan semuanya punya key itu. Begitu tim redaksi mulai menerbitkan artikel asli, tidak satu pun artikel punya _demo_source_id, dan INNER JOIN itu dengan patuh membuang semuanya. Kategorinya tidak pernah kosong. Query-nya yang dicurangi.

Yang bikin bug ini awet: dia tidak pernah error. Tidak ada warning, tidak ada fatal, tidak ada apa-apa di log. Query-nya valid, hasilnya nol, dan template merender state kosong dengan anggun. Setiap komponen "bekerja".

Perbaikannya

Prinsipnya satu: konten asli tidak boleh bergantung pada artefak seeder. Filter meta itu saya buat kondisional, hanya aktif dalam konteks demo yang eksplisit. Kalau tim tidak butuh mode demo lagi, hapus saja sekalian:

add_action( 'pre_get_posts', function ( $query ) {
    if ( ! $query->is_category() || ! $query->is_main_query() ) {
        return;
    }
 
    // Seeder metadata is a dev-only concern. Never require it in production.
    if ( defined( 'THEME_DEMO_PREVIEW' ) && THEME_DEMO_PREVIEW ) {
        $query->set( 'meta_query', array(
            array(
                'key'     => '_demo_source_id',
                'compare' => 'EXISTS',
            ),
        ) );
    }
} );

Setelah itu, found_posts langsung melaporkan angka yang benar dan grid terisi seperti seharusnya.

Bug bonus: thumbnail kategori yang tidak pernah muncul

Selagi di file yang sama, saya menemukan bug kedua yang masih satu keluarga. Situs ini punya opsi thumbnail per kategori, dan tidak pernah ada satu pun yang tampil. Sekarang jelas kenapa:

// Admin side, saving the thumbnail:
update_option( 'category_thumb_' . $term_id, $attachment_id );
 
// Front end, reading it back:
$thumb_id = get_option( 'category_thumbnail_' . $term_id ); // always false

Kode yang menyimpan memakai satu pola nama option, kode yang membaca memakai pola lain. Setter dan getter tidak pernah menunjuk baris yang sama, jadi get_option selalu balik false dan template jatuh ke placeholder, selamanya, tanpa suara. Ini spesies yang sama dengan bug pertama: string key yang diketik tangan di dua tempat, lalu drift.

Perbaikannya: satu sumber kebenaran.

function category_thumbnail_option_name( $term_id ) {
    return 'category_thumb_' . (int) $term_id;
}
 
// Setter and getter now share one source of truth:
update_option( category_thumbnail_option_name( $term_id ), $attachment_id );
$thumb_id = get_option( category_thumbnail_option_name( $term_id ) );

Sekarang setter dan getter tidak mungkin drift, karena nama option-nya lahir dari satu tempat.

Checklist

  • Listing kosong tapi kontennya ada? Jangan menebak. Dump $query->request dan baca SQL-nya. Cari JOIN ke wp_postmeta yang tidak kamu harapkan.
  • Tema yang dibangun di atas data demo sering diam-diam hard-depend pada metadata seeder. Konten produksi tidak boleh disyaratkan membawa artefak seeder, titik.
  • Setiap string key yang dipakai di lebih dari satu tempat, entah nama option, meta key, atau transient, milik satu konstanta atau helper. Dua salinan yang diketik tangan pasti akan berbeda suatu hari.

Sejak kasus ini, setiap kali melihat angka nol di tempat yang seharusnya ramai, pertanyaan pertama saya bukan lagi "ke mana kontennya", tapi "apa yang diam-diam disyaratkan query ini".