# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Apa ini

Smart Redirect Proxy (SRP) — sistem traffic-routing afiliasi berbasis PHP 8.3. **Tanpa framework**: PHP prosedural yang dirangkai dengan `require`/`include`. Composer dipakai hanya untuk library pihak ketiga di `vendor/` (Guzzle, MaxMind GeoIP2, Mobile_Detect). Dideploy di shared hosting cPanel/LiteSpeed; bukan repo git.

## Perintah

```bash
composer install                   # library pihak ketiga ke vendor/
composer check                     # gate penuh: cs + stan + test
composer cs                        # lint PSR-12 (hanya error) — read-only, dipakai gate/CI
composer fix                       # auto-format PHP-CS-Fixer (PER-CS + strict types) — MENULIS file, bukan dry-run
composer stan                      # PHPStan (level max, sadar-baseline)
composer test                      # PHPUnit (tanpa coverage)
composer coverage                  # PHPUnit + text coverage (butuh Xdebug/PCOV)

vendor/bin/phpunit --filter <Name> # jalankan satu test/method
php redirect/update-geoip.php       # refresh GeoLite2-Country.mmdb
php redirect/cleanup-cache.php      # purge cache basi srp_bb/
php migrations/<nama>.php           # migrasi = skrip PHP standalone, dijalankan manual (002, 003, 004 tersedia); dukungan --dry-run bervariasi per-skrip, cek file dulu

# Generate auto-secrets (jalankan sekali saat setup):
php -r "foreach(['AF_SECRET','IXG_BYPASS_KEY','SRP_API_KEY','POSTBACK_SECRET'] as \$k) echo \$k.'='.bin2hex(random_bytes(32)).PHP_EOL;"
# Generate RD_PASSWORD_HASH untuk redirect-decision.php:
php -r "echo password_hash('REPLACE_WITH_STRONG_PASSWORD', PASSWORD_BCRYPT, ['cost'=>12]), PHP_EOL;"
```

**Status quality-gate:** konfigurasi sudah ada dan terpasang (`phpunit.xml`, `phpstan.neon` + `phpstan-baseline.neon`, `.php-cs-fixer.dist.php`); `tests/` berisi unit suite untuk core redirect (`bootstrap.php` + `Base64UrlTest`, `RedirectPayloadTest`, `SanitizeTest`, `FilterEngineTest`, `GeoResolverTest` — yang terakhir men-skip kasus berbasis DB saat `redirect/databases/GeoLite2-Country.mmdb` tidak ada — serta `FunctionsTest` yang menguji helper murni di `redirect/functions.php`, dan `MeetupClickLogTest` untuk jalur pencatatan klik `_meetups/`). Dengan `require-dev` terpasang, `composer check` penuh hijau (terverifikasi 2026-06-27): `composer cs` lulus, `composer test` lulus (64 tests, 128 assertions), `composer stan` lulus — baseline-nya mengunci **859 temuan pre-existing** di `level: max`, jadi hanya isu *baru* yang gagal; regenerasi setelah cleanup dengan `vendor/bin/phpstan analyse --generate-baseline` (memakai path `phpstan.neon` yang tidak menyertakan `imgp_handler.php`, sedangkan script `composer stan`/`composer cs` menambahkannya). `composer cs` juga memakai `--ignore` untuk 4 file template HTML-mixed (`public/addondomain/index.php`, `public/campaigns/index.php`, `public/index.php`, `statistics/clicks/index.php`): phpcs `Generic.WhiteSpace.ScopeIndent` dan php-cs-fixer (PER-CS) saling bertentangan pada indentasi blok PHP-di-dalam-HTML, jadi keempatnya dikecualikan dari phpcs tetapi **tetap** dicakup `composer fix` + PHPStan. Script Composer memakai bentuk portabel `@php vendor/bin/<tool>` agar berjalan di Windows maupun Linux tanpa bergantung pada proxy bin per-OS — `vendor/` yang dibundel dari host Linux tidak punya wrapper `.bat`. Tidak ada section `autoload` di Composer, sehingga bootstrap PHPUnit dan `bootstrapFiles` PHPStan memuat `vendor/autoload.php` hanya untuk library pihak ketiga — class `Srp\Redirect\*` tetap di-`require_once` secara eksplisit.

## Arsitektur

### Tiga modul, satu database, routing subdomain
`.htaccess` di root me-routing berdasarkan header `Host` **sebelum** PHP berjalan, memetakan tiap subdomain ke sebuah direktori (men-set `SUBROUTED:1` untuk menghentikan pass rewrite kedua):

| Subdomain | Dir | Peran |
|---|---|---|
| `gen.<domain>` | `public/` | Panel admin (tracker, campaign, addon domain, shortlink, konfigurasi Cloudflare) |
| `s.<domain>` | `statistics/` | Panel statistik + penerima postback |
| `r.<domain>` | `redirect/` | Engine redirect (setiap klik masuk) |
| wildcard lain | root `index.php` → `redirect/index.php` | Entry afiliasi addon-domain; `.htaccess` me-rewrite `/{TOKEN}` → `?sub_id={TOKEN}` |

Ketiga modul berbagi DB MySQL yang sama (tabel di `schema.sql`: `generate`, `addondomain`, `offering`, `clickrecord`, `leadreport`, `srp_short_links`, `shortlinks`).

### Bootstrap bersama (file level-root, dipakai semua modul)
- `connection_pdo.php` — **satu-satunya** PDO factory. `$pdo = require __DIR__.'/connection_pdo.php';` mengembalikan PDO terkonfigurasi (ERRMODE_EXCEPTION, FETCH_ASSOC, EMULATE_PREPARES=false, MULTI_STATEMENTS=false). Berisi shim kompat-mysqli `dbObj` yang deprecated — pakai PDO langsung di kode baru.
- `env.php` — loader `.env` + accessor `app_env()`, `app_required_env()`. Presedensi env: env asli > `.env`. Juga memuat helper auto-deteksi host cPanel.
- `Base64URL.php`, `ip_address.php` (`getUserIP()`) — utilitas bersama kecil.
- `.env` adalah satu-satunya sumber konfigurasi; edit manual atau via editor browser standalone `env-config.php` (endpoint paling sensitif — bisa menulis ulang setiap secret).

### Alur engine redirect (`redirect/index.php`)
Ini jalur panas (hot path); baca dulu sebelum menyentuh logika redirect. Urutan penting dan sensitif keamanan:
1. Bootstrap (autoload + `require_once` eksplisit `functions.php`, `GeoResolver.php`, `FilterEngine.php`, `public_link.php`, `redirect_payload.php`), lalu security headers.
2. Rate limit per-IP berbasis file (60 req/min, token bucket di `sys_get_temp_dir()/srp_bb/`). Bot sosial dikecualikan agar OG scraper selalu dapat meta tag.
3. Deteksi bot sosial **sebelum** lookup geo (melewati MaxMind yang mahal untuk crawler).
4. Geo + blokir negara (`SRP_BLOCK_COUNTRIES`, default `ID`).
5. Ekstraksi & validasi token (`sub_id`, ketat `[A-Za-z0-9_-]`, ≤2048) — divalidasi **sebelum** cek filter agar token buruk selalu 400 dan tidak pernah bocor ke jalur filter.
6. Mode filter berwaktu (`FilterEngine`): siklus berulang 2-menit filter / 3-menit normal; trafik mobile non-VPN di jendela filter diarahkan ke `SRP_FILTER_URL`. Deteksi VPN/proxy/Tor via Cloudflare `T1` + header proxy + `blackbox.ipinfo.app` (cache 1 jam).
7. Decode token: token CSV base64url legacy (`srp_public_link_decode_legacy_token`) ATAU short code `s-<code>` (lookup DB `srp_short_link_find`, fallback `srp_shortlinks_find`).
8. Emit: bot sosial → `srp_render_og()`; user nyata → payload terenkode (`srp_redirect_payload_encode`) ke `/_meetups/?rk=...`, baik `direct` (302) atau `landing` (HTML via `srp_render_landing`).

`redirect/_meetups/` adalah **sink payload** yang menutup loop (bukan URL mati): `index.php` men-decode `rk` (`srp_redirect_payload_decode`), men-sanitasi field (`sanitize.php`), men-dedup per-klik di cache `srp_bb/` (TTL 5-menit, sweep malas ~1% agar bersih sendiri tanpa cron), mencatat klik, lalu re-encode dan meneruskan via `r.php`. `clicks.php` adalah jalur pencatatan klik. Ia memakai PDO factory sendiri `redirect/connection.config.php`, bukan `connection_pdo.php` root.

### Entry point root standalone (di luar routing tiga-modul)
Ini diakses langsung, bukan via pemetaan subdomain — ketahui keberadaannya sebelum mengasumsikan `redirect/`, `public/`, atau `statistics/` yang memiliki suatu perilaku:
- `install.php` (~94 KB web installer) mem-bootstrap `schema.sql`; `migrations/*.php` dijalankan manual setelahnya (mis. `002_hash_generate_passwords.php`). Installer dan migrasi adalah satu-satunya jalur DDL.
- `health.php` — probe liveness/readiness. `legal.php`, `robots.php`, `sitemap.php` — permukaan kepatuhan/SEO yang dilayani dari root. `shortlink.php` — resolver shortlink level-root.
- `404.php` — landing page standalone bermerk **WrongURL** (CSP nonce + security headers sendiri); aset merek ada di `assets/` (favicon, logo) plus `wrong-url-fox.jpg`. `install.cmd` adalah bootstrap installer Claude Code untuk Windows, **bukan** bagian dari aplikasi SRP.
- `login_throttle.php` — helper rate-limit login bersama yang dipakai alur auth admin/stats (berbasis file, area temp `srp_bb/` yang sama).
- File `php.ini` / `.user.ini` per-direktori meng-override setting runtime PHP per modul di LiteSpeed — cek ini sebelum men-debug limit (memory, ukuran upload, tampilan error). `.env.lock` menjaga penulisan `.env` bersamaan dari `env-config.php`.

`GeoResolver` dan `FilterEngine` adalah class gaya PSR-4 `Srp\Redirect\*` tetapi dimuat via **`require_once` eksplisit**, bukan autoload Composer (composer.json tidak punya section `autoload`). `index.php` mempertahankan fungsi wrapper `srp_*` yang deprecated di sekitarnya untuk back-compat.

### Integrasi & API modul admin (`public/`) — catatan penting
Sisi admin punya pasangan tersendiri dari engine redirect. Pahami file berikut sebelum mengubah perilaku addon-domain, sertifikat, atau shortlink:
- **`public/redirect-decision.php`** — panel runtime untuk mengontrol siklus timed-filter **tanpa deploy**: menulis `filter_timing.json` + `filter_url.txt` ke `srp_bb/` cache dir, yang dibaca oleh `redirect/index.php` saat menentukan jendela filter/normal. Membutuhkan `RD_PASSWORD_HASH` di `.env` (generate dengan perintah di atas); tanpa itu endpoint menolak semua akses.
- **Dua root API, bukan satu.** `redirect/api/` hanya berisi `shorten.php` (URL shortener, auth `SRP_API_KEY`). Root API sebenarnya ada di **`public/api/`** — endpoint user-portal/admin: provisioning Cloudflare (`cf_sync.php`, `cf_create_token.php`, `cf-diagnose.php`, `user_cf_config.php`), manajemen addon domain cPanel (`cpanel_addon_lib.php`, `user_insert_domain.php`, `user_del_domain.php`, `checkdomain.php`), dan shortener eksternal (`tinyurl.php`, `is.gd.php`, `ixg.php`/`ixgeu.php`).
- **`cf-lib.php`** — library Cloudflare API bersama, di-include oleh `addondomain/cf.php` (admin) dan `api/cf_sync.php` (user portal). Tiap fungsi menerima `$token` opsional; jika kosong, fallback ke env `CF_API_TOKEN`. Entry point provisioning zona: `cfApplyAllRecommended($zoneId, $token)`.
- **`xmlapi.php`** — class client cPanel XML-API/UAPI (port 2083) untuk operasi addon domain. Ini satu-satunya jalur outbound ke cPanel; `cf-lib.php` + `public/api/cf_*` satu-satunya jalur outbound ke Cloudflare.
- **`session_guards.php`** — helper hardening sesi/HTTPS yang sadar Cloudflare (`HTTP_CF_VISITOR`, `X-Forwarded-Proto`), dipakai untuk gating sesi admin.

### Jalur shorten ada tiga (auth berbeda)
- `redirect/api/shorten.php` — endpoint API resmi, butuh `SRP_API_KEY` (Bearer/`?key=`).
- `redirect/shorten-web.php` — endpoint form browser (memuat `.env` langsung, set security headers sendiri).
- `redirect/shorten-client.php` — **CLI tool**, bukan endpoint web; membuat short link via `POST /api/shorten` dari `--sub-id` atau fields. `redirect/generate-short-link.php` adalah jalur generasi terkait.

### Image proxy terduplikasi
Image proxy: logika tunggal ada di **`imgp_handler.php`** (root). `public/imgp.php` dan `redirect/imgp.php` kini hanya shim tipis yang `require __DIR__ . '/../imgp_handler.php'` — jadi keduanya tak bisa drift. Edit perilaku **hanya** di `imgp_handler.php`, jangan inline di shim. Fungsi: fetch gambar remote, re-serve same-origin agar OG scraper (Facebook dkk) bisa baca gambar yang diblok CORS/geo/bot, resize maks 1200×630, re-encode JPEG ≤85q jika GD ada (`/imgp?u=<base64url-url>`). `imgp_handler.php` diblokir dari akses web langsung via blocklist `.htaccess`.

## Konvensi (ekspektasi yang ditegakkan di codebase ini)
- `declare(strict_types=1);` di awal setiap file PHP; PSR-12; signature bertipe.
- Semua akses DB via PDO prepared statement (jangan pernah SQL hasil konkatenasi string).
- Catch `Throwable`; log via `error_log()`; kembalikan error generik yang aman (`Internal Server Error`) ke klien — jangan pernah bocorkan detail DB/stack.
- State cache dan rate-limit berada di bawah `sys_get_temp_dir()/srp_bb/` (`SRP_CACHE_DIR_NAME`).
- Cron wajib untuk korektness: `setup-cron.sh` memasang cleanup cache (harian 02:00) + update GeoIP (Rabu 03:00).

## Konteks auth (lihat auth.md, README)
- **Admin** — `public/login.php` via `ADMIN_PASSWORD`. Portal per-tracker memakai bcrypt `generate.password`; dashboard bisa menampilkan plaintext (`password_plain`) tetapi login memverifikasi hash bcrypt.
- **Stats** — `statistics/login.php` (hash bcrypt di `statistics/report_auth.php`).
- **Editor `.env`** — `env-config.php` (`A2ROOT_PASSWORD` → `ADMIN_PASSWORD` → `ENV_EDITOR_PASSWORD`).
- **API** — `redirect/api/shorten.php` (URL shortener) memakai `SRP_API_KEY` statis sebagai `Authorization: Bearer` atau `?key=`.

## Dokumen lain
- `INSTALL.md` — deployment penuh cPanel/Cloudflare/GeoIP.
- `auth.md` — konteks auth, penggunaan API key, endpoint discovery `.well-known/`.
- `AGENTS.md` — doc kembar CLAUDE.md untuk Codex; isi paralel, jaga sinkron bila salah satunya diubah.
- `build/` — artefak rilis zip (clean-pack, clean-production-pack, production security-hardening); output build, bukan sumber.
- `PACK-MANIFEST.md` — manifest rilis terkini: apa yang disertakan, apa yang dikecualikan (vendor/, .env, GeoIP DB), dan urutan first-deploy (`composer install --no-dev`, `schema.sql`, migrasi pending, update-geoip).
- `.env.example` — mendokumentasikan setiap key konfigurasi.
- `AGENT-READINESS.md` — disposisi temuan agent-discovery (isitagentready.com): hanya `Content-Signal` yang diadopsi; sisanya ditutup *not-applicable by design* karena SRP anti-bot.
- `SECURITY-AUDIT-2026-06-22.md` — catatan hardening yang sudah diterapkan: blokir PHP di `public/dist/`, hapus static fallback hash dari `redirect-decision.php`, bersihkan path cPanel dari `.htaccess`.
