D
P
0
← Semua artikel Read in English

WordPress & PHP di Produksi

Form WordPress Mentok di `admin-post.php` Kosong? Warning `open_basedir restriction in effect` Mendahului Header

· · 5 menit baca
Form WordPress Mentok di `admin-post.php` Kosong? Warning `open_basedir restriction in effect` Mendahului Header

Baris datanya tersimpan, tapi halaman suksesnya tidak pernah datang. Itu yang terjadi pada 11 Oktober 2026 di situs WordPress klien yang sedang saya kerjakan di WordPress Studio, aplikasi untuk menjalankan WordPress di mesin sendiri. Setiap kiriman form berakhir di admin-post.php yang kosong, tanpa redirect.

Gejala setengah jalan begini terlihat persis seperti handler yang rusak. Penyimpanannya berhasil, jadi kirimannya jelas sampai ke handler. Redirect-nya tidak terjadi, jadi wajar kalau kecurigaan jatuh ke sisa kode handler atau ke tema. Penyebabnya ternyata ada di luar keduanya, yaitu satu mu-plugin uji sementara yang hari itu mencatat panggilan wp_mail ke sebuah berkas log di bawah /private/tmp.

Respons mentahnya menunjuk ke alat uji

Petunjuknya ada di respons mentah, bukan di halaman yang kosong: sebuah warning PHP yang tercetak sebelum header. Bunyinya seperti ini. Folder situsnya saya ganti dengan penanda, dan bagian yang memang sudah terpotong di catatan saya biarkan seperti adanya.

file_put_contents(): open_basedir restriction in effect ... is not within the allowed path(s):
(<root situs>:/var/folders/.../T:/Applications/Studio.app/Contents/Resources/cli/php/router.php:...)

Warning itu datang dari file_put_contents(), dari penulisan log oleh mu-plugin uji tadi. Isinya menyatakan bahwa pembatasan open_basedir sedang berlaku dan path tujuannya tidak termasuk path yang diizinkan. open_basedir adalah pengaturan PHP yang membatasi direktori mana saja yang boleh dibuka oleh fungsi berkas, dan PHP di Studio berjalan dengan open_basedir yang dibatasi ke root situs dan satu folder temp.

Daftar path yang diizinkan di warning itu memuat root situs, sebuah folder di /var/folders/.../T, dan router.php di dalam Studio.app, lalu sisanya terpotong. Folder di bawah /var/folders adalah folder temp per pengguna milik macOS, dan itu bukan /tmp. Di macOS, /tmp hanyalah symlink ke /private/tmp, dan menurut warning itu sendiri, berkas log di bawah /private/tmp berada di luar path yang diizinkan. Tempat yang paling lazim untuk berkas sementara ternyata justru tempat yang tidak boleh disentuh PHP di Studio.

Kenapa barisnya tersimpan tapi redirect-nya hilang

Studio menampilkan warning, jadi teks warning itu ikut terkirim sebagai keluaran. Header HTTP harus berangkat sebelum body, dan begitu ada keluaran yang benar-benar terkirim, header ikut terkirim bersamanya. Header yang baru mau ditambahkan sesudah itu sudah terlambat. Itulah yang menimpa wp_safe_redirect di sini: dia tidak lagi bisa mengirim header Location-nya. Jadi redirect-nya bukan salah arah. Dia tidak pernah sempat terkirim.

Separuh cerita lainnya ada di urutan handler. Dua form di situs itu, form pendaftaran di template landing dan form newsletter di footer, sama-sama mengirim POST ke admin-post.php dengan field tersembunyi action yang sama. Handler-nya dikaitkan ke dua hook, satu untuk pengunjung yang belum login dan satu untuk yang sudah. Bentuk yang disederhanakan, dengan nama action dan handler yang saya ganti:

// Disederhanakan. Nama action dan handler sudah diganti.
add_action( 'admin_post_nopriv_demo_signup', 'demo_handle_signup' );
add_action( 'admin_post_demo_signup', 'demo_handle_signup' );
 
// Di dalam handler, urutannya begini:
$back = wp_get_referer() ? wp_get_referer() : home_url( '/' );
// ...
$post_id = wp_insert_post( $postarr, true ); // entri disimpan sebagai post 'private'
// ...
wp_mail( /* ... */ );
wp_safe_redirect( add_query_arg( $flag_key, 'success', $back ) . $anchor );
exit;

Di cabang yang menyimpan entri secara penuh, entri masuk lebih dulu lewat wp_insert_post sebagai post berstatus private. Blok penyimpanannya berakhir di baris 233, sedangkan wp_mail baru dipanggil di baris 293, enam puluh baris kemudian (293 dikurangi 233). Karena mu-plugin uji itu mencatat panggilan wp_mail, lognya baru ditulis ketika wp_mail dipanggil, dan pada saat itu entrinya sudah ada di basis data. wp_safe_redirect dengan flag success datang tepat sesudah wp_mail, berarti tepat di belakang warning. Setiap kiriman sempat menyimpan barisnya lalu kehilangan redirect-nya, dan satu warning dari alat uji cukup untuk merusak setiap redirect yang datang sesudahnya.

PHP yang melayani form bukan PHP di terminal

Ada satu detail lain yang gampang terlewat. Header respons x-powered-by menyebut PHP/8.3.33, jadi request web di Studio dilayani PHP 8.3, bukan biner CLI 8.4 yang dipakai untuk lint. php -l hanya memeriksa sintaks, dan pemeriksaan itu pun dijalankan oleh PHP yang lain. Kode yang lolos lint di terminal tetap bisa tersandung aturan runtime PHP yang melayani request, seperti open_basedir tadi.

Sebaliknya, terminal tetap berguna untuk membaca isi situsnya. php -r atau skrip yang me-require wp-load.php jalan dengan biner CLI Studio dan drop-in SQLite yang terpasang di situs itu, yaitu wp-content/db.php dari SQLite integration versi 1.8.0. Jalur itu yang dipakai untuk mendaftar dan menghapus baris uji.

Aturan untuk kode uji di Studio

Ada tiga aturan yang saya pegang sekarang setiap kali menambahkan kode uji sisi server di Studio.

Pertama, mu-plugin uji atau keluaran debug sisi server apa pun ditulis di dalam root situs, misalnya di bawah uploads, dan tidak pernah ke /private/tmp. Di repo situs ini, wp-content/uploads/ sudah masuk .gitignore, jadi berkas log di sana tidak ikut ter-commit.

// Contoh, bukan berkas proyek. Log uji ditulis di dalam root situs,
// yang termasuk path yang diizinkan open_basedir, bukan ke /private/tmp.
file_put_contents( WP_CONTENT_DIR . '/uploads/debug-test.log', $line . "\n", FILE_APPEND );

Kedua, kalau form lokal tiba-tiba "gagal" sesudah ada kode uji yang ditambahkan, kirim ulang POST-nya dengan curl -D - dan baca body-nya sebelum mencurigai tema. Opsi -D - menulis header respons ke keluaran standar, jadi status, header, dan body terbaca berurutan di satu layar. Redirect yang sehat kelihatan dari header Location. Kalau ada warning yang mendahului header, teks warning itu ada di body, dan header Location yang ditunggu tidak muncul.

# Disederhanakan. Isi --data dengan field yang sama persis seperti kiriman form aslinya.
curl -s -D - --data "action=demo_signup&..." "$SITE/wp-admin/admin-post.php"

Ketiga, mu-plugin uji dan berkas lognya dihapus begitu selesai dipakai. Mu-plugin dimuat di setiap request, termasuk di produksi kalau sampai ikut terunggah. Di repo ini folder wp-content/mu-plugins/ juga masuk .gitignore, jadi berkas yang tertinggal di sana tidak akan muncul di git status, dan tidak ada yang mengingatkan bahwa dia masih ada. Per 14 Oktober, folder itu hanya berisi 99-studio-loader.php, loader mu-plugin milik Studio sendiri, dan folder sqlite-database-integration. Mu-plugin uji tadi sudah tidak ada di sana.

Kalau saya ringkas, kejadian ini bukan soal open_basedir yang galak. Mu-plugin dimuat di setiap request, jadi kode uji berjalan di request yang sama dengan kode yang diujinya dan tunduk pada aturan PHP yang sama. Ketika form lokal rusak tepat sesudah kode uji ditambahkan, yang pertama dibaca adalah respons mentahnya, bukan tema.