Skip to content

Fix: Menampilkan media Galeri berbasis link (Google Drive & YouTube) di Web dan Admin - #1766

Merged
vickyrolanda merged 3 commits into
rilis-devfrom
feat/album_support_video
Oct 2, 2026
Merged

vickyrolanda merged 3 commits into
rilis-devfrom
feat/album_support_video

Conversation

@pandigresik

Copy link
Copy Markdown
Contributor

Pull Request: Fix: Menampilkan media Galeri berbasis link (Google Drive & YouTube) di Web dan Admin

Description

Galeri dengan jenis = url hanya mengenali tautan YouTube, sehingga tautan Google Drive untuk foto maupun video tidak pernah ditampilkan, dan halaman detail galeri mengabaikan link sama sekali. PR ini menambahkan resolusi link media (Google Drive, YouTube, Vimeo, serta berkas foto/video langsung) beserta tipe medianya, lalu merender <img>, <video>, atau <iframe> yang sesuai pada Admin maupun halaman web.

Changes made:

  1. Feature (Service): Menambahkan app/Services/MediaLinkService.php untuk resolusi link media (deteksi + probing HTTP saat perlu)
  2. Feature (Value Object + Enum): Menambahkan app/ValueObjects/MediaLink.php dan app/Enums/TipeMedia.php sebagai kontrak tipe media
  3. Database: Menambahkan migration 2026_09_28_000001_add_media_type_to_galeris_table.php (kolom galeris.media_type)
  4. Feature (Model): app/Models/Galeri.php — menambahkan media_type, media_url, media_embed_url pada $appends agar tersedia di API
  5. Feature (Controller): app/Http/Controllers/Publikasi/GaleriController.php — deteksi & penyimpanan media_type pada store()/update(), serta kolom Media pada DataTable
  6. Feature (Request): app/Http/Requests/GaleriRequest.php — validasi link harus URL http(s) dan media_type harus salah satu tipe yang diizinkan
  7. Feature (View): Menambahkan resources/views/components/galeri-media.blade.php (renderer media bersama) dan resources/views/publikasi/galeri/parts/media-cell.blade.php
  8. Feature (View): resources/views/publikasi/galeri/_form.blade.php — menambahkan select Tipe Media (Otomatis/Foto/Video/YouTube) dan pratinjau media pada form Tambah/Ubah
  9. Feature (View + JavaScript): public/js/custom.js — menambahkan renderGaleriMedia() dan isGaleriPlayable() yang dipakai kedua tema
  10. Fix (View): themes/opendk/default & themes/opendk/buen — halaman detail galeri kini merender media berbasis link, bukan hanya array gambar
  11. Fix (View): themes/opendk/default & themes/opendk/buen — halaman daftar galeri menampilkan thumbnail + badge Video/YouTube sesuai tipe
  12. Refactor + Security (Middleware): app/Http/Middleware/SecurityHeaders.php — CSP dipisah ke app/Support/CspPolicy.php, ditambahkan media-src https: blob: dan frame-src https://drive.google.com agar media eksternal tidak diblokir di production

Reason for change:

  • Akar masalah 1 — deteksi tipe: Galeri::getGambarPathAttribute() hanya memiliki regex YouTube. Tautan Google Drive tidak pernah dikenali sehingga selalu jatuh ke no-image.png.
  • Akar masalah 2 — link Drive tidak informatif: URL Google Drive tidak memuat ekstensi berkas, sehingga aplikasi tidak bisa membedakan foto dan video hanya dari URL. Ini membuat probing HTTP saat penyimpanan, ditambah pilihan tipe manual sebagai jaring pengaman.
  • Akar masalah 3 — halaman detail mengabaikan link: galeri_detail.blade.php hanya melakukan iterasi galeri.gambar. Untuk galeri jenis = url, nilai tersebut null, sehingga selalu menampilkan "Tidak ada gambar yang ditampilkan!" di kedua tema.
  • Akar masalah 4 — CSP memblokir media: media-src 'self' dan frame-src tanpa drive.google.com membuat video eksternal tidak dapat diputar di environment production.
  • Mengapa render remote, bukan unduh ke server: media tidak diunduh ke storage sehingga tidak menambah beban disk pada server desa, dan tautan asli tetap utuh untuk dihapus/diubah pengguna.

Impact of change:

✅ Video Google Drive & YouTube tampil di halaman detail galeri (Web) dan dapat diputar langsung dari admin
✅ Konsistensi tampilan: media berbasis link kini diperlakukan sama dengan media hasil upload di Admin maupun Web
✅ Deteksi otomatis: admin tidak wajib memilih tipe untuk tautan yang ekstensinya jelas (.jpg, .mp4, YouTube)
✅ Penanganan kasus Drive: foto Drive dipetakan ke uc?export=view, video Drive dipetakan ke preview file/d/ID/preview yang mendukung pemutaran
✅ Validasi lebih ketat: tautan yang bukan URL http(s) ditolak saat menyimpan, bukan gagal diam-diam saat tampil
✅ CSP lebih dirawat: directive CSP kini berada di kelas terpisah dan dapat diuji per directive

Related Issue

  • Solution for fix related to issue #1764

Steps to Reproduce

Before fix (problem):

  1. Login ke halaman Admin pada keclongikis.paserkab.go.id
  2. Masuk ke menu Album → pilih album → Tambah → Selengkapnya
  3. Tambahkan media Upload foto (foto ✅), Link Google Drive foto (⚠️ hanya tertaut, tidak tertampil di detail), Link Google Drive video (❌)
  4. Simpan data
  5. Buka halaman Web → galeri album → detail galeri
  6. ❌ Galeri berbasis link menampilkan "Tidak ada gambar yang ditampilkan!" / no-image.png; video Drive tidak pernah bisa diputar
  7. ❌ Halaman Admin tidak memiliki pratinjau maupun kolom media untuk link

After fix (solution):

  1. Login ke halaman Admin
  2. Masuk ke menu Album → pilih album → Tambah → Selengkapnya
  3. Tambahkan media Upload foto (✅ tetap berfungsi), Link Google Drive foto (✅ tampil sebagai gambar), Link Google Drive video (✅ terdeteksi video dan diputar lewat preview resmi Drive)
  4. Simpan data — untuk tautan tanpa ekstensi, request HEAD ke Google Drive dipakai untuk memastikan apakah isinya foto atau video; bila probing gagal, admin dapat memilih Tipe Media → Video secara manual
  5. Buka halaman Web → detail galeri
  6. ✅ Galeri berbasis link menampilkan foto, pemutar video, atau YouTube embed sesuai tipenya
  7. ✅ Halaman Admin menampilkan pratinjau media pada form Ubah dan kolom Media pada tabel daftar galeri

Testing on related features:

  • Album (daftar album, thumbnail album) ✅ tidak berubah, gambar_path untuk jenis = file tetap memakai storage lokal
  • Galeri jenis = file (upload banyak foto) ✅ kompatibel penuh
  • Galeri jenis = url non-media (mis. tautan biasa) ✅ tetap disimpan, hanya menampilkan tautan tanpa embed
  • Galeri Observer (hapus berkas saat gambar dihapus/diubah) ✅ tidak berubah
  • API Frontend /api/frontend/v1/galeri ✅ respons kini memuat media_type, media_url, media_embed_url
  • Tema default dan buen ✅ keduanya sudah disesuaikan
  • Upload file dan validasi valid_file ✅ tidak berubah

Checklist

  • I have complied with script writing rules
  • I have followed pull request review process
  • I have created unit test/integration test to verify the fix
  • Manual testing has been done in development environment
  • No console errors or warnings
  • Code has been reviewed by at least 1 person

Technical Details

Technical Explanation

Alur resolusi link

MediaLinkService::resolve() bersifat murni (tanpa jaringan) dan mengubah link menjadi MediaLink berisi type, id, url, embedUrl, thumbnail:

Sumber Type url (src) embedUrl (iframe) thumbnail
YouTube (watch, youtu.be, embed, shorts, live) youtube url asli youtube.com/embed/{id} img.youtube.com/vi/{id}/hqdefault.jpg
Google Drive foto image drive.google.com/uc?export=view&id= — sama dengan url
Google Drive video video drive.google.com/uc?export=download&id= drive.google.com/file/d/{id}/preview —
Vimeo video url asli player.vimeo.com/video/{id} —
*.jpg/.png/.gif/.webp/... image url asli — url asli
*.mp4/.webm/.ogv/.mov/... video url asli — —
lainnya unknown url asli — —

Mengapa ada probing HTTP

// Link Google Drive tidak memuat ekstensi berkas, sehingga "image" di sana
// hanya asumsi dan harus dipastikan lewat pemeriksaan HTTP. Tanpa ini, video
// Google Drive akan salah diperlakukan sebagai foto dan gagal ditampilkan.
$needsProbe = $resolved->type === TipeMedia::Unknown
    || ($resolved->type === TipeMedia::Image && $this->isGoogleDrive((string) $url));

probe() mengirim HEAD (timeout 5 detik, maksimal 3 redirect) dan membaca Content-Type; bila generik (application/octet-stream, hal yang sering terjadi pada Drive), ia jatuh ke Content-Disposition untuk melihat ekstensi nama berkas. Untuk link Drive, target probing adalah uc?export=download&id= karena /file/d/ID/view hanya menyajikan halaman HTML. Kegagalan probing tidak menggagalkan penyimpanan — tipe kembali ke hasil resolusi.

Probing tidak pernah dijalankan bila admin memilih tipe secara manual, sehingga form tetap berfungsi di lingkungan tanpa akses jaringan keluar.

Model

protected $appends = ['gambar_path', 'media_type', 'media_url', 'media_embed_url'];

Kolom media_type yang tersimpan selalu lebih dipercaya daripada menebak ulang dari URL; gambar_path kini diturunkan dari media()->thumbnail sehingga tetap kompatibel dengan tema lama.

Rendering

  • Admin (server-side Blade): <x-galeri-media :galeri="$galeri" variant="thumb|full" />
  • Web (client-side JavaScript): renderGaleriMedia(galeri, options) di public/js/custom.js, dipakai kedua tema sehingga tidak ada duplikasi logika

Perbaikan URL storage

asset('storage/publikasi/galeri/') membuang garis miring di akhir, sehingga penggabungan langsung dengan nama berkas akan menghasilkan .../galerifoto.jpg. Sekarang base URL dihitung terpisah dan pemisah ditambahkan eksplisit:

var storageBaseUrl = @json(asset('storage/publikasi/galeri'));
// ...
var imagePath = storageBaseUrl + '/' + item;

Configuration changes

Tidak ada perubahan pada file .env atau config/. CSP dipindahkan dari string satu baris di middleware ke konstanta App\Support\CspPolicy::DIRECTIVES dengan dua directive yang diperluas:

'media-src' => ["'self'", 'https:', 'blob:'],   // sebelumnya: 'self'
// frame-src ditambahkan: https://drive.google.com

📝 Catatan keamanan: media-src https: dipilih agar video dari hosting situs desa mana pun bisa diputar, konsisten dengan img-src yang sudah memakai wildcard *. Kalau ingin lebih ketat, daftar host video dapat disempitkan.

Dependencies added

No new dependencies

Testing

Manual Testing

  • Tambah galeri dengan upload foto — foto tampil di daftar dan detail (regression)
  • Tambah galeri dengan link Google Drive foto — tampil sebagai gambar
  • Tambah galeri dengan link Google Drive video — tampil pemutar video
  • Tambah galeri dengan link YouTube — tampil embed, bisa diputar
  • Tambah galeri dengan link file langsung .mp4 / .jpg — tampil sesuai tipe
  • Ubah Tipe Media → Video manual untuk link Drive yang probing-nya gagal
  • Isi link dengan teks bukan URL — ditolak dengan pesan validasi
  • Ubah galeri dari file ke url — gambar lama terhapus, media_type terisi
  • Ubah galeri dari url ke file — link dan media_type dikosongkan
  • Verifikasi kolom Media pada tabel daftar galeri (Admin)
  • Verifikasi pratinjau media pada form Ubah (Admin)
  • Verifikasi halaman detail galeri pada tema default dan buen
  • Regression: album, galeri upload, dan galeri tautan biasa tetap berfungsi

Automated Testing

  • tests/Unit/Services/MediaLinkServiceTest.php — 27 test (YouTube, Google Drive, file langsung, Vimeo, fallback, probing, detectType)
  • tests/Unit/Models/GaleriTest.php — 8 test (accessor media_type / media_url / media_embed_url dan serialisasi API)
  • tests/Unit/Support/CspPolicyTest.php — 5 test (per directive CSP)
  • tests/Feature/GaleriControllerTest.php — 19 test (9 test baru: Drive video/foto, manual override, validasi, perpindahan file↔url, pratinjau form, kolom DataTable)
  • tests/Feature/Api/Frontend/GaleriControllerTest.php — 4 test baru (bentuk payload media pada API)
  • tests/Feature/PublikasiGaleriPublikTest.php — 4 test baru (render halaman publik, URL storage tanpa regresi separator)

Total: 95 test lulus, 273 assertion pada 9 file yang relevan.

Catatan: terdapat 13 kegagalan test yang sudah ada sebelumnya dan tidak berkaitan dengan PR ini (ModelScopesTest, InternalApiTest, FileUploadTest, ArtikelControllerTest, AuthorizationTest, FilePreviewFeatureTest, PendudukExportTest) — sudah diverifikasi menghasilkan output identik ketika seluruh kode PR ini dihapus.

Browser Compatibility

  • Chrome/Edge (Chromium) — menunggu verifikasi manual
  • Firefox — menunggu verifikasi manual
  • Safari — menunggu verifikasi manual

Screenshots / Video

Belum tersedia. Disarankan menyertakan perekaman layar untuk: (1) form Tambah Galeri dengan pratinjau link video Google Drive, (2) halaman detail galeri di Web yang memutar video, (3) tampilan tema buen.

Before:

  • Galeri detail berbasis link menampilkan pesan "Tidak ada gambar yang ditampilkan!"
  • Admin tidak memiliki pratinjau media maupun kolom Media

After:

  • Galeri detail menampilkan foto / pemutar video / embed YouTube sesuai tipe
  • Admin menampilkan pratinjau media pada form dan kolom Media pada tabel

Breaking Changes

None secara fungsional — kolom galeris.media_type bersifat nullable dan seluruh field lama (gambar_path, link, jenis) tetap tidak berubah perilakunya.

⚠️ Satu perubahan perilaku yang disengaja: validasi link kini memerlukan URL http/https. Galeri lama yang menyimpan link non-URL tidak ikut terhapus, namun tidak dapat diedit tanpa memperbaikinya lebih dahulu.

Migration Guide

Wajib menjalankan migration pada server yang telah terpasang:

php artisan migrate --force

Migration menambahkan kolom galeris.media_type (nullable, panjang 20). Tidak ada data yang diubah atau dihapus.

📝 Catatan untuk data lama: baris galeri yang sudah ada akan bernilai NULL pada media_type.

  • Tautan YouTube tetap tampil benar tanpa migrasi data, karena tipe diturunkan ulang dari URL saat render.
  • Tautan Google Drive lama perlu disimpan ulang (Ubah → Simpan) agar tipe terdeteksi, atau diisi manual dengan SQL berikut:
UPDATE galeris SET media_type = 'video' WHERE jenis = 'url' AND link LIKE '%drive.google.com%';

Baris yang tidak ada di kategori tersebut dapat dibiarkan NULL (bermapped ke unknown).

References


Additional notes:

  1. Scope: Album thumbnail (albums.gambar) sengaja tidak diubah — hanya galeri yang menjadi objek laporan. Dapat dijadikan PR terpisah bila diperlukan.
  2. Saran review: MediaLinkService::probe() melakukan outbound HTTP saat penyimpanan galeri. Mohon konfirmasi apakah server desa diizinkan melakukan request keluar ke drive.google.com.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI PR Review sedang antri di server...

Proses review akan segera dimulai di background — hasil akan muncul sebagai komentar setelah selesai.
Powered by CrewAI · PR #1766

@vickyrolanda
vickyrolanda merged commit 7696f53 into rilis-dev Oct 2, 2026
@vickyrolanda
vickyrolanda deleted the feat/album_support_video branch October 2, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants