diff --git a/app/Models/File.php b/app/Models/File.php index 39a21e1..407741d 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Setting; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -42,10 +43,6 @@ class File extends Model return $this->src; } - /** - * ساخت URL عمومی از مسیر نسبی storage/... - * قبلاً: env('domain') + path — بدون اسلش بین دامنه و مسیر خراب می‌شد. - */ public static function publicUrl(?string $path): ?string { if ($path === null || trim($path) === '') { @@ -58,8 +55,64 @@ class File extends Model return $trimmed; } - $domain = rtrim((string) (env('domain') ?: config('app.url')), '/'); + $base = self::resolvePublicBaseUrl(); + if ($base === '') { + return null; + } - return $domain.'/'.ltrim($trimmed, '/'); + $relative = ltrim($trimmed, '/'); + if (! str_starts_with($relative, 'storage/')) { + $relative = 'storage/'.$relative; + } + + return $base.'/'.$relative; + } + + /** + * پایهٔ URL فایل‌های storage — cache-safe و مستقل از env() خام. + */ + public static function resolvePublicBaseUrl(): string + { + $domain = config('app.domain'); + if (is_string($domain) && $domain !== '' && ! str_contains($domain, 'localhost')) { + return rtrim($domain, '/'); + } + + $appUrl = config('app.url'); + if (is_string($appUrl) && $appUrl !== '' && ! str_contains($appUrl, 'localhost') && str_contains($appUrl, '/server')) { + return rtrim($appUrl, '/'); + } + + try { + $rows = Setting::query() + ->whereIn('key', ['bace_url', 'server_url', 'admin_url', 'app_url']) + ->where('is_active', 1) + ->pluck('value', 'key'); + + foreach (['bace_url', 'server_url'] as $key) { + $value = trim((string) ($rows[$key] ?? '')); + if ($value !== '' && preg_match('/^https?:\/\//i', $value)) { + return rtrim($value, '/'); + } + } + + $adminUrl = trim((string) ($rows['admin_url'] ?? '')); + if ($adminUrl !== '' && preg_match('#/admin#i', $adminUrl)) { + return rtrim(preg_replace('#/admin/?$#i', '/server', rtrim($adminUrl, '/')), '/'); + } + + $siteUrl = trim((string) ($rows['app_url'] ?? '')); + if ($siteUrl !== '' && preg_match('/^https?:\/\//i', $siteUrl)) { + return rtrim($siteUrl, '/').'/server'; + } + } catch (\Throwable) { + // DB unavailable during deploy/migrate + } + + if (is_string($appUrl) && $appUrl !== '' && ! str_contains($appUrl, 'localhost')) { + return rtrim($appUrl, '/'); + } + + return ''; } } diff --git a/config/app.php b/config/app.php index 1affae8..6e00c51 100644 --- a/config/app.php +++ b/config/app.php @@ -54,6 +54,9 @@ return [ 'url' => env('APP_URL', 'http://localhost'), + /** پایهٔ URL فایل‌های public — معمولاً https://example.ir/server */ + 'domain' => env('domain'), + 'asset_url' => env('ASSET_URL', null), /* diff --git a/tests/Unit/FilePublicUrlTest.php b/tests/Unit/FilePublicUrlTest.php new file mode 100644 index 0000000..4d595e8 --- /dev/null +++ b/tests/Unit/FilePublicUrlTest.php @@ -0,0 +1,37 @@ +assertSame( + 'https://garoosigholaktala.ir/server/storage/1405/02/27/sample.jpg', + $url + ); + } + + public function test_public_url_derives_server_base_from_admin_url_setting(): void + { + Config::set('app.domain', null); + Config::set('app.url', 'http://localhost'); + + \App\Models\Setting::query()->updateOrCreate( + ['key' => 'admin_url'], + ['value' => 'https://garoosigholaktala.ir/admin/', 'is_active' => 1, 'title_fa' => 'admin'] + ); + + $base = File::resolvePublicBaseUrl(); + + $this->assertSame('https://garoosigholaktala.ir/server', $base); + } +}