# Image Fallback & Custom Layout Fixes

Dokumentasi ringkas bagi dua perubahan utama yang dibuat pada CMS:
1. Sistem fallback gambar (`image_url()` helper) menggantikan picsum + `Storage::url()`.
2. Pembetulan "Override with custom layout" supaya page jadi **fully standalone**.

---

## 1. Image Helper (`image_url()`)

**Fail:** `app/Helpers/ImageHelper.php`
**Daftar:** `composer.json` → `autoload.files` (dah `composer dump-autoload`).

Helper universal yang handle 4 situasi:

```php
function image_url($value, $default = null) {
    if (empty($value))
        return asset($default ?? 'dynaweb4/img/No_Image_Available.jpg');
    if (str_starts_with($value, 'http'))
        return $value;                       // URL luar (picsum lama, dll)
    if (str_starts_with($value, 'dynaweb4/'))
        return asset($value);                // public/dynaweb4/...
    return \Illuminate\Support\Facades\Storage::url($value); // storage/app/public
}
```

- Fallback gambar: `public/dynaweb4/img/No_Image_Available.jpg`
- (Nota: helper `video_url()` pernah wujud tapi **dibuang** atas permintaan — video guna `No_Video_Available.png` tidak lagi dipakai.)

### Kenapa tukar dari `Storage::url()`?
- Gambar module (slider, article, gallery, video, dll) disimpan di **`public/dynaweb4/img/module/*.jpg`** (folder awam, tidak perlu `storage:link`).
- `Storage::url('dynaweb4/img/module/x.jpg')` menjana `…/storage/dynaweb4/img/module/x.jpg` → **404**.
- `image_url()` membaca tempat betul + ada fallback bila null.

### Apa yang ditukar
- **8 backend module views** (edit/index): contentArticle, contentSlider, contentDownload, contentImage, contentVideo, contentPhotoGallery, contentApplication, contentPhotoList → guna `image_url()`.
- **Semua 13 storage blade** (`storage/app/private/PHP/*.blade.php`) yang guna `Storage::url()` ditukar ke `image_url()`:
  `article_web`, `directory_web`, `download_web`, `example_web`, `gallery_web`,
  `home_web`, `mod_downloads`, `news_web`, `publication_web`, `slider_2d`,
  `slider_3d`, `slider_web`, `video_web` (dan `mod_*` detail yang ditukar lebih awal).
- **6 seeder module** (ContentNews, ContentDownload, ContentGallery, ContentImage, ContentSlider, ContentVideo) → path picsum diganti dengan `dynaweb4/img/module/*.jpg` (45 gambar CC Wikimedia, Putrajaya/KL tanpa orang, dimuat turun ke `public/dynaweb4/img/module/`).

### Gambar seeder
- 45 fail jpg di `public/dynaweb4/img/module/` (semua >1KB).
- Dimuat turun guna Wikimedia Commons API (temp script `openverse_fetch.php` — **sudah dipadam**).

---

## 2. Custom Layout = Fully Standalone

**Masalah:** Bila `use_custom_layout = 1` (cth: auth pages dari `FrontendAuthPagesSeeder`),
page tidak jadi standalone. `PortalHandler` fallback ke **site layout** bila
`LAYOUTS/PAGE/{site}/{slug}.blade.php` tidak wujud → header/footer site muncul →
styling rosak bila deploy ke Nginx (CSS variable `var(--accent)` dll tidak define).

**Fix:** `app/Services/PortalHandler.php` (~line 316)

```php
if ($page->use_custom_layout) {
    // Fully standalone: render PAGE_CONTENT terus, abaikan site layout/header/footer
    if ($pageContentBlade) {
        $html = $pageHtml;
    } else {
        $pageLayout = Storage::get("LAYOUTS/PAGE/{$siteSlug}/{$page->slug}.blade.php");
        $html = $pageLayout ? Blade::render($pageLayout, $layoutVars) : Blade::render($siteLayout, $layoutVars);
    }
} elseif ($siteLayout) {
    $html = Blade::render($siteLayout, $layoutVars);
} else {
    $html = $pageHtml;
}
```

**Fix CSS variable:** `database/seeders/portal/FrontendAuthPagesSeeder.php`
- Tambah `:root { --accent, --gray-* … }` ke dalam `<style>` auth page & dashboard
  supaya self-contained (tidak bergantung global CSS).
- Tambah reset `html, body { margin:0 !important; padding:0 !important; }` supaya
  gradient cover full screen (hilang "border putih" dari default body margin).
- Tambah `@import url('https://fonts.bunny.net/css?family=figtree…')` + `font-family:'Figtree'`
  supaya font sama dengan backend (cantik, tidak default).

---

## 3. Fix Component Render (`PortalHandler`)

**Masalah:** `frontend_page_components` tidak ada column `code` — code disimpan di
table `frontend_components` (relationship `component_fk`). `PortalHandler` asal baca
`$comp->code` (null) → `Storage::get("PHP/.blade.php")` gagal → component tidak render.

**Fix:** `app/Services/PortalHandler.php` (~line 242)
```php
$compCode = $comp->component ? $comp->component->code : $comp->code;
$blade = Storage::get("PHP/{$compCode}.blade.php");
```

---

## 4. migrate:fresh --seed

Untuk reset macam project baru:
```
php artisan migrate:fresh --seed
```
- Semua seeder jalan (termasuk `WebSiteBackupSeeder`) — selamat sebab table kosong.
- `WebSiteBackupSeeder` guna `DB::table()->insert()` (bukan insertOrIgnore), jadi
  **jangan run berulang kali ke atas DB yang sudah ada data** (akan duplicate).
  Untuk reseed selective, exclude `WebSiteBackupSeeder`.

---

---

## 5. Server Deployment & Permissions (Linux + Nginx)

Bila upload ke server, pastikan:

### Folder wajib WRITABLE (web server user = `www-data`)
```
storage/                    (app, framework, logs)
storage/app/private/        ← PENTING: BladeSyncService tulis mod_*.blade.php & PAGE_CONTENT
storage/app/private/PHP/
storage/app/private/PAGE_CONTENT/
bootstrap/cache/
```

### Folder cuma perlu READABLE
```
public/dynaweb4/            ← gambar module (web server baca je)
public/ (termasuk public/storage symlink)
vendor/ app/ config/ resources/ dll
```

### Command standard (Ubuntu/Nginx)
```bash
sudo chown -R www-data:www-data storage bootstrap/cache
sudo find storage bootstrap/cache -type f -exec chmod 644 {} \;
sudo find storage bootstrap/cache -type d -exec chmod 755 {} \;
php artisan storage:link
```

### Checklist upload
1. **`public/dynaweb4/img/module/*.jpg`** — upload sekali (gambar fizikal, bukan DB). readable 644.
2. **`storage/app/private/PHP/*.blade.php`** — upload sekali. Folder mesti writable.
3. **`public/dynaweb4/img/No_Image_Available.jpg`** — upload (fallback image).
4. **`.env`** — set `APP_URL`, `APP_ENV=production`, db credentials.
5. **`composer install --optimize-autoloader`** — regenerate autoload (helper `image_url()` daftar di `composer.json`).
6. `php artisan key:generate` + `php artisan config:cache`.

### Masalah kerap kat Nginx
- `storage/app/private` x writable → component/page x render.
- `public/dynaweb4` x upload → gambar 404.
- `No_Image_Available.jpg` x upload → fallback broken.
- CSS variable (`var(--accent)`) — dah self-contained dalam auth/dashboard page, x bergantung global CSS.

---

## Catatan / TODO
- `ContentSliderSeeder.php` masih ada variabel `$picsumUrl` (nilai sudah lokal) —
  boleh dinamakan semula ke `$localImg` untuk kekemasan (tidak fungsi).
- `PAGE_CONTENT/web/` kosong selepas fresh seed kerana page biasa (homepage) simpan
  component di `frontend_page_components`, bukan column `page_content`. Ini normal —
  component di-render dari `storage/app/private/PHP/` oleh `PortalHandler`.
- Pastikan `public/dynaweb4/img/module/*.jpg` di-commit ke git (gambar fizikal, bukan DB).
- `php artisan` di environment ini ada isu `package:discover` (exit -1073741502) tapi
  `migrate`/`db:seed`/`tinker` berfungsi.
