diff --git a/app/Http/Controllers/Admin/FileController.php b/app/Http/Controllers/Admin/FileController.php index cb620b1..6256233 100644 --- a/app/Http/Controllers/Admin/FileController.php +++ b/app/Http/Controllers/Admin/FileController.php @@ -1,83 +1,73 @@ -formatDate()); - $disk = Storage::disk('public'); - - if (! $disk->exists($path)) { - $disk->makeDirectory($path); - $disk->makeDirectory($path.'/thumb'); - } - - return $path; - } - - public function store(Request $request) - { - $uploaded = $request->file('file'); - if (! $uploaded || ! $uploaded->isValid()) { - return response()->json(['message' => 'فایل بارگذاری نشد یا خالی است'], 422); - } - if ($uploaded->getSize() < 1) { - return response()->json(['message' => 'فایل خالی است'], 422); - } - - $path = $this->createDir(); - $data = $request->all(); - $data['original_name'] = $uploaded->getClientOriginalName(); - $data['ext'] = $uploaded->getClientOriginalExtension() - ?: $uploaded->extension() - ?: 'jpg'; - if (! isset($data['title']) || is_null($data['title'])) { - $data['title'] = $data['original_name']; - } - $file_name_src = md5(time().$data['original_name']); - $file_name = $file_name_src.'.'.$data['ext']; - $data['has_thumb'] = false; - $data['size'] = $uploaded->getSize(); - $stored = Storage::disk('public')->putFileAs($path, $uploaded, $file_name); - if (! $stored) { - return response()->json(['message' => 'ذخیره فایل روی سرور ناموفق بود'], 500); - } - - $storageFile = storage_path('app/public/'.$path.'/'.$file_name); - $publicFile = public_path('storage/'.$path.'/'.$file_name); - if (! is_file($publicFile) && is_file($storageFile)) { - $publicDir = dirname($publicFile); - if (! is_dir($publicDir)) { - mkdir($publicDir, 0755, true); - } - @copy($storageFile, $publicFile); - } - - $data['src'] = 'storage/'.$path.'/'.$file_name; - $data['user_id'] = auth()->id(); - $data['sts'] = 1; - $relativePath = $data['src']; - $file_create = File::create($data); - $fullSrc = File::publicUrl($relativePath); - - $payload = $file_create->fresh()->toArray(); - $payload['path'] = $relativePath; - $payload['full_src'] = $fullSrc; - $payload['src'] = $fullSrc; - - return response()->json($payload); - } -} +formatDate()); + $disk = Storage::disk('public'); + + if (! $disk->exists($path)) { + $disk->makeDirectory($path); + $disk->makeDirectory($path.'/thumb'); + } + + return $path; + } + + public function store(Request $request) + { + $uploaded = $request->file('file'); + if (! $uploaded || ! $uploaded->isValid()) { + return response()->json(['message' => 'فایل بارگذاری نشد یا خالی است'], 422); + } + if ($uploaded->getSize() < 1) { + return response()->json(['message' => 'فایل خالی است'], 422); + } + + $path = $this->createDir(); + $data = $request->all(); + $data['original_name'] = $uploaded->getClientOriginalName(); + $data['ext'] = $uploaded->getClientOriginalExtension() + ?: $uploaded->extension() + ?: 'jpg'; + if (! isset($data['title']) || is_null($data['title'])) { + $data['title'] = $data['original_name']; + } + $file_name_src = md5(time().$data['original_name']); + $file_name = $file_name_src.'.'.$data['ext']; + $data['has_thumb'] = false; + $data['size'] = $uploaded->getSize(); + $stored = Storage::disk('public')->putFileAs($path, $uploaded, $file_name); + if (! $stored || ! Storage::disk('public')->exists($stored)) { + return response()->json(['message' => 'ذخیره فایل روی سرور ناموفق بود'], 500); + } + + $data['src'] = 'storage/'.$stored; + $data['user_id'] = auth()->id(); + $data['sts'] = 1; + $relativePath = $data['src']; + $file_create = File::create($data); + $fullSrc = File::publicUrl($relativePath); + + $payload = $file_create->fresh()->toArray(); + $payload['path'] = $relativePath; + $payload['full_src'] = $fullSrc; + $payload['src'] = $fullSrc; + + return response()->json($payload); + } +} diff --git a/app/Models/File.php b/app/Models/File.php index 5a4abc6..70f5df0 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -109,9 +109,15 @@ class File extends Model /** * پایهٔ URL فایل‌های storage. + * در درخواست HTTP، دامنهٔ همان درخواست اولویت دارد (جلوگیری از URL اشتباه وقتی settings دامنهٔ ماژول دیگر است). */ public static function resolvePublicBaseUrl(): string { + $fromRequest = self::baseUrlFromRequest(); + if ($fromRequest !== '') { + return $fromRequest; + } + $fromSettings = self::baseUrlFromSettings(); if ($fromSettings !== '') { return self::ensureAbsoluteUrl($fromSettings); @@ -127,6 +133,11 @@ class File extends Model return self::ensureAbsoluteUrl(self::ensureServerPublicBase(rtrim($appUrl, '/'))); } + return ''; + } + + private static function baseUrlFromRequest(): string + { try { if (app()->bound('request')) { $request = request(); diff --git a/tests/Unit/FilePublicUrlTest.php b/tests/Unit/FilePublicUrlTest.php index 9dda6f8..fff74cb 100644 --- a/tests/Unit/FilePublicUrlTest.php +++ b/tests/Unit/FilePublicUrlTest.php @@ -4,52 +4,76 @@ namespace Tests\Unit; use App\Models\File; use App\Models\Setting; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Config; use Tests\TestCase; class FilePublicUrlTest extends TestCase { - private function seedGaroosiAdminUrl(): void + private function bindRequest(string $host, string $path = '/server/admin/file'): Request + { + $request = Request::create('https://'.$host.$path, 'POST'); + $this->app->instance('request', $request); + + return $request; + } + + private function expectedServerBase(string $host): string + { + return 'https://'.$host.'/server'; + } + + private function seedServerUrlSetting(string $host): void { Setting::query()->updateOrCreate( - ['key' => 'admin_url'], - ['value' => 'https://garoosigholaktala.ir/admin/', 'is_active' => 1, 'title_fa' => 'admin'] + ['key' => 'server_url'], + ['value' => $this->expectedServerBase($host), 'is_active' => 1, 'title_fa' => 'server'] ); } - public function test_public_url_uses_server_storage_path_from_site_domain(): void + private function seedAdminUrlSetting(string $host): void { - $this->seedGaroosiAdminUrl(); - Config::set('app.domain', 'https://garoosigholaktala.ir'); + Setting::query()->updateOrCreate( + ['key' => 'admin_url'], + ['value' => 'https://'.$host.'/admin/', 'is_active' => 1, 'title_fa' => 'admin'] + ); + } + + public function test_public_url_uses_request_host_for_storage_path(): void + { + $host = 'tenant-a.test'; + $this->bindRequest($host); $url = File::publicUrl('storage/1405/02/27/sample.jpg'); $this->assertSame( - 'https://garoosigholaktala.ir/server/storage/1405/02/27/sample.jpg', + $this->expectedServerBase($host).'/storage/1405/02/27/sample.jpg', $url ); } - public function test_public_url_rewrites_legacy_wrong_absolute_url(): void + public function test_public_url_rewrites_legacy_wrong_absolute_url_using_request_host(): void { - $this->seedGaroosiAdminUrl(); - Config::set('app.domain', 'https://garoosigholaktala.ir'); + $host = 'tenant-a.test'; + $this->bindRequest($host); - $url = File::publicUrl('https://garoosigholaktala.ir/storage/1405/02/27/sample.jpg'); + $url = File::publicUrl('https://'.$host.'/storage/1405/02/27/sample.jpg'); $this->assertSame( - 'https://garoosigholaktala.ir/server/storage/1405/02/27/sample.jpg', + $this->expectedServerBase($host).'/storage/1405/02/27/sample.jpg', $url ); } - public function test_resolve_base_from_admin_url_setting(): void + public function test_resolve_base_from_admin_url_setting_when_no_http_request(): void { Config::set('app.domain', null); Config::set('app.url', 'http://localhost'); - $this->seedGaroosiAdminUrl(); - $this->assertSame('https://garoosigholaktala.ir/server', File::resolvePublicBaseUrl()); + $host = 'tenant-b.test'; + $this->seedAdminUrlSetting($host); + + $this->assertSame($this->expectedServerBase($host), File::resolvePublicBaseUrl()); } public function test_public_url_falls_back_to_relative_path_when_base_unknown(): void @@ -67,9 +91,25 @@ class FilePublicUrlTest extends TestCase Config::set('app.domain', null); Config::set('app.url', 'http://localhost'); - $request = \Illuminate\Http\Request::create('https://niazitala.ir/server/admin/file', 'POST'); - $this->app->instance('request', $request); + $host = 'tenant-c.test'; + $this->bindRequest($host); - $this->assertSame('https://niazitala.ir/server', File::resolvePublicBaseUrl()); + $this->assertSame($this->expectedServerBase($host), File::resolvePublicBaseUrl()); + } + + public function test_request_host_wins_over_stale_settings_domain(): void + { + $staleHost = 'stale-tenant.test'; + $requestHost = 'current-tenant.test'; + + $this->seedServerUrlSetting($staleHost); + $this->bindRequest($requestHost); + + $url = File::publicUrl('storage/1405/04/15/sample.png'); + + $this->assertSame( + $this->expectedServerBase($requestHost).'/storage/1405/04/15/sample.png', + $url + ); } }