أُنشئ من Tokal/Test
Remove booking pricing and fix venue image delivery
هذا الالتزام موجود في:
@@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class VenueController extends Controller
|
||||
@@ -77,6 +78,21 @@ class VenueController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
// PUBLIC FILE PROXY (serves files from storage/app/public without relying on /public/storage symlink)
|
||||
public function publicFile(string $path)
|
||||
{
|
||||
$normalized = ltrim(trim($path), '/');
|
||||
if ($normalized === '' || str_contains($normalized, '..')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if (! Storage::disk('public')->exists($normalized)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return Storage::disk('public')->response($normalized);
|
||||
}
|
||||
|
||||
// CREATE VENUE (vendor only)
|
||||
public function store(Request $request)
|
||||
{
|
||||
@@ -453,7 +469,8 @@ class VenueController extends Controller
|
||||
if ($request->hasFile('images')) {
|
||||
foreach ($request->file('images') as $image) {
|
||||
$path = $image->store('venues', 'public');
|
||||
$urls[] = rtrim($request->getSchemeAndHttpHost(), '/') . '/storage/' . ltrim($path, '/');
|
||||
// Store a relative public-storage path to avoid environment-specific hosts (localhost/127.0.0.1).
|
||||
$urls[] = '/storage/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +585,11 @@ class VenueController extends Controller
|
||||
private function resolveImageUrls(Venue $venue): array
|
||||
{
|
||||
if ($this->hasInlineVenueArrays()) {
|
||||
return is_array($venue->image_urls) ? $venue->image_urls : [];
|
||||
return collect(is_array($venue->image_urls) ? $venue->image_urls : [])
|
||||
->map(fn ($url) => $this->normalizePublicAssetUrl($url))
|
||||
->filter(fn ($url) => $url !== '')
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
if ($this->hasStructuredVenueData()) {
|
||||
@@ -579,6 +600,8 @@ class VenueController extends Controller
|
||||
return ($venue->images ?? collect())
|
||||
->sortBy('sort_order')
|
||||
->pluck('url')
|
||||
->map(fn ($url) => $this->normalizePublicAssetUrl($url))
|
||||
->filter(fn ($url) => $url !== '')
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
@@ -589,7 +612,18 @@ class VenueController extends Controller
|
||||
private function resolveOffers(Venue $venue): array
|
||||
{
|
||||
if ($this->hasInlineVenueArrays()) {
|
||||
return is_array($venue->offers) ? $venue->offers : [];
|
||||
return collect(is_array($venue->offers) ? $venue->offers : [])
|
||||
->map(function ($offer) {
|
||||
if (! is_array($offer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$offer['image_url'] = $this->normalizePublicAssetUrl($offer['image_url'] ?? null) ?: null;
|
||||
return $offer;
|
||||
})
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
if ($this->hasStructuredVenueData()) {
|
||||
@@ -601,7 +635,7 @@ class VenueController extends Controller
|
||||
->map(fn (Offer $offer) => [
|
||||
'title' => $offer->title,
|
||||
'description' => $offer->description,
|
||||
'image_url' => $offer->image_url,
|
||||
'image_url' => $this->normalizePublicAssetUrl($offer->image_url) ?: null,
|
||||
'is_active' => (bool) $offer->is_active,
|
||||
])
|
||||
->values()
|
||||
@@ -712,4 +746,105 @@ class VenueController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizePublicAssetUrl(mixed $value): string
|
||||
{
|
||||
$raw = trim((string) ($value ?? ''));
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$appUrl = (string) config('app.url', '');
|
||||
$appHost = parse_url($appUrl, PHP_URL_HOST);
|
||||
$appScheme = parse_url($appUrl, PHP_URL_SCHEME) ?: 'https';
|
||||
|
||||
// Relative public storage paths -> proxy route
|
||||
if (str_starts_with($raw, '/storage/')) {
|
||||
return $this->publicStorageProxyUrl(substr($raw, strlen('/storage/')));
|
||||
}
|
||||
if (str_starts_with($raw, 'storage/')) {
|
||||
return $this->publicStorageProxyUrl(substr($raw, strlen('storage/')));
|
||||
}
|
||||
|
||||
if (! preg_match('#^https?://#i', $raw)) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
$parts = parse_url($raw);
|
||||
if (! is_array($parts) || empty($parts['host'])) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
$host = strtolower((string) $parts['host']);
|
||||
$path = (string) ($parts['path'] ?? '');
|
||||
|
||||
// Any URL pointing to /storage/* should be served through the API proxy to avoid /storage symlink issues.
|
||||
if (str_starts_with($path, '/storage/')) {
|
||||
return $this->publicStorageProxyUrl(substr($path, strlen('/storage/')));
|
||||
}
|
||||
|
||||
// Rewrite local/private hosts to the configured app host.
|
||||
if ($this->isLocalOrPrivateHost($host) && ! empty($appHost)) {
|
||||
$port = parse_url($appUrl, PHP_URL_PORT);
|
||||
$rewritten = $parts;
|
||||
$rewritten['scheme'] = $appScheme;
|
||||
$rewritten['host'] = (string) $appHost;
|
||||
if ($port !== null) {
|
||||
$rewritten['port'] = (int) $port;
|
||||
} else {
|
||||
unset($rewritten['port']);
|
||||
}
|
||||
return $this->buildUrlFromParts($rewritten);
|
||||
}
|
||||
|
||||
// Prefer HTTPS for hosted domains and the current app host.
|
||||
$isHostedGhaymah = str_ends_with($host, '.hosted.ghaymah.systems');
|
||||
if (($isHostedGhaymah || (! empty($appHost) && $host === strtolower((string) $appHost)))
|
||||
&& (($parts['scheme'] ?? 'http') === 'http')) {
|
||||
$parts['scheme'] = 'https';
|
||||
return $this->buildUrlFromParts($parts);
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
|
||||
private function publicStorageProxyUrl(string $relativePath): string
|
||||
{
|
||||
$clean = ltrim($relativePath, '/');
|
||||
if ($clean === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$base = rtrim((string) config('app.url', ''), '/');
|
||||
if ($base === '') {
|
||||
$base = request()->getSchemeAndHttpHost();
|
||||
}
|
||||
|
||||
return $base . '/api/public-files/' . str_replace('%2F', '/', rawurlencode($clean));
|
||||
}
|
||||
|
||||
private function isLocalOrPrivateHost(string $host): bool
|
||||
{
|
||||
if (in_array($host, ['localhost', '127.0.0.1', '::1', '0.0.0.0'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preg_match('/^(10\\.|192\\.168\\.|172\\.(1[6-9]|2\\d|3[0-1])\\.)/', $host) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function buildUrlFromParts(array $parts): string
|
||||
{
|
||||
$scheme = (string) ($parts['scheme'] ?? 'https');
|
||||
$host = (string) ($parts['host'] ?? '');
|
||||
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
|
||||
$path = (string) ($parts['path'] ?? '');
|
||||
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
|
||||
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
|
||||
|
||||
return $scheme . '://' . $host . $port . $path . $query . $fragment;
|
||||
}
|
||||
}
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم