Files
animexe/app/Http/Controllers/Admin/AdController.php
T
2026-07-14 00:01:48 +03:00

188 lines
7.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ad;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class AdController extends Controller
{
public function index()
{
$ads = Ad::orderByDesc('created_at')->get();
$settings = [
'vad_enabled' => Setting::get('vad_enabled', '0'),
'vad_freq_episodes' => Setting::get('vad_freq_episodes', 2),
'vad_freq_minutes' => Setting::get('vad_freq_minutes', 5),
'vad_upsell_percent' => Setting::get('vad_upsell_percent', 20),
'banner_ads_enabled' => Setting::get('banner_ads_enabled', '0'),
];
$stats = [
'total_impressions' => $ads->sum('impressions'),
'total_clicks' => $ads->sum('clicks'),
'avg_ctr' => $ads->sum('impressions') > 0
? round($ads->sum('clicks') / $ads->sum('impressions') * 100, 2) : 0,
'active_count' => $ads->where('is_active', true)->count(),
];
return view('admin.ads.index', compact('ads', 'settings', 'stats'));
}
public function store(Request $request)
{
$data = $this->validateAd($request);
if ($request->hasFile('media_file')) {
$data['file_path'] = $this->storeMedia($request->file('media_file'));
}
unset($data['media_file']);
Ad::create($data);
return back()->with('success', 'Reklam eklendi.');
}
public function edit(Ad $ad)
{
return view('admin.ads.edit', compact('ad'));
}
public function update(Request $request, Ad $ad)
{
$data = $this->validateAd($request, $ad);
if ($request->hasFile('media_file')) {
$newPath = $this->storeMedia($request->file('media_file'));
if ($ad->file_path) Storage::disk('public')->delete($ad->file_path);
$data['file_path'] = $newPath;
}
unset($data['media_file']);
$ad->update($data);
return redirect()->route('admin.ads.index')->with('success', 'Reklam güncellendi.');
}
public function destroy(Ad $ad)
{
if ($ad->file_path) Storage::disk('public')->delete($ad->file_path);
$ad->delete();
return back()->with('success', 'Reklam silindi.');
}
public function toggle(Ad $ad)
{
$ad->update(['is_active' => !$ad->is_active]);
return back()->with('success', $ad->is_active ? 'Reklam aktifleştirildi.' : 'Reklam durduruldu.');
}
public function saveSettings(Request $request)
{
Setting::set('vad_enabled', $request->boolean('vad_enabled') ? '1' : '0', 'ads');
Setting::set('vad_freq_episodes', max(1, (int) $request->input('vad_freq_episodes', 2)), 'ads');
Setting::set('vad_freq_minutes', max(1, (int) $request->input('vad_freq_minutes', 5)), 'ads');
Setting::set('vad_upsell_percent', min(100, max(0, (int) $request->input('vad_upsell_percent', 20))), 'ads');
Setting::set('banner_ads_enabled', $request->boolean('banner_ads_enabled') ? '1' : '0', 'ads');
return back()->with('success', 'Reklam ayarları kaydedildi.');
}
private function validateAd(Request $request, ?Ad $existing = null): array
{
$type = $request->input('type', 'video');
// Sunucu upload limitini aşan dosya: PHP boş/bozuk upload gönderir.
// Sessizce medyasız reklam kaydetmek yerine net hata ver.
$this->guardUploadError($request);
// Yüklenmiş dosya da dış URL de yoksa reklam gösterilemez (media_url null olur).
// Düzenlemede mevcut dosya varsa yeniden yükleme zorunlu değil.
$hasExisting = $existing?->file_path || $existing?->external_url;
$needsMedia = !$request->hasFile('media_file') && !$hasExisting;
$data = $request->validate([
'name' => 'required|string|max:120',
'type' => 'required|in:video,banner',
'placement' => 'required|in:preroll,home_mid,home_bottom',
'media_file' => [
'nullable', 'file',
$type === 'video' ? 'mimes:mp4,m4v' : 'mimes:jpg,jpeg,png,webp,gif',
$type === 'video' ? 'max:102400' : 'max:20480', // video 100MB, görsel/gif 20MB
],
'external_url' => [$needsMedia ? 'required' : 'nullable', 'nullable', 'url', 'max:2000'],
'click_url' => 'nullable|url|max:2000',
'skip_after' => 'required|integer|min:0|max:60',
'weight' => 'required|integer|min:1|max:100',
'is_active' => 'boolean',
'starts_at' => 'nullable|date',
'ends_at' => 'nullable|date|after:starts_at',
], [
'external_url.required' => 'Bir medya dosyası yükleyin veya dış URL girin. '
. 'Dosya seçtiyseniz sunucu yükleme limitini aşmış olabilir (maks. '
. ini_get('upload_max_filesize') . ').',
'media_file.mimes' => $type === 'video'
? 'Video dosyası MP4 formatında olmalı.'
: 'Görsel JPG, PNG, WebP veya GIF formatında olmalı.',
'media_file.max' => 'Dosya çok büyük.',
]);
// Checkbox işaretli değilse request'te hiç gelmez — açıkça boolean'a çevir
$data['is_active'] = $request->boolean('is_active');
return $data;
}
/** PHP upload hatalarını (limit aşımı, kısmi yükleme) net mesajla yüzeye çıkar. */
private function guardUploadError(Request $request): void
{
$file = $request->file('media_file');
if (!$file || $file->isValid()) {
return;
}
$msg = match ($file->getError()) {
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE =>
'Dosya sunucunun yükleme limitini aşıyor (maks. ' . ini_get('upload_max_filesize')
. '). Daha küçük bir dosya seçin veya hosting limitini yükseltin.',
UPLOAD_ERR_PARTIAL => 'Dosya yalnızca kısmen yüklendi, lütfen tekrar deneyin.',
UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE =>
'Sunucu dosyayı geçici klasöre yazamadı. Hosting sağlayıcınıza bildirin.',
default => 'Dosya yüklenemedi (hata kodu: ' . $file->getError() . ').',
};
throw \Illuminate\Validation\ValidationException::withMessages(['media_file' => $msg]);
}
/** Dosyayı public diske yaz ve tam yazıldığını doğrula. */
private function storeMedia(\Illuminate\Http\UploadedFile $file): string
{
// NOT: klasör adı bilerek nötr ('ads' değil) — adblocker /media/ads/ yolunu
// ERR_BLOCKED_BY_CLIENT ile engelliyor. 'content' engellenmez.
$expected = $file->getSize();
$path = $file->store('content', 'public');
if (!$path || !Storage::disk('public')->exists($path)) {
throw \Illuminate\Validation\ValidationException::withMessages([
'media_file' => 'Dosya sunucuya kaydedilemedi. storage/app/public klasörünün yazma izni olduğundan emin olun.',
]);
}
// Kısmi yazma (disk dolu / kesilen upload) sessizce bozuk reklam bırakmasın
$written = Storage::disk('public')->size($path);
if ($expected > 0 && $written !== $expected) {
Storage::disk('public')->delete($path);
throw \Illuminate\Validation\ValidationException::withMessages([
'media_file' => "Dosya eksik yüklendi ({$written}/{$expected} byte). Tekrar deneyin.",
]);
}
return $path;
}
}