2026-07-09 13:37:03 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
|
|
|
|
|
class File extends Model
|
|
|
|
|
{
|
|
|
|
|
use HasFactory, Selector;
|
|
|
|
|
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
'original_name',
|
|
|
|
|
'title',
|
|
|
|
|
'ext',
|
|
|
|
|
'src',
|
|
|
|
|
'size',
|
|
|
|
|
'user_id',
|
|
|
|
|
'type',
|
|
|
|
|
'has_thumb',
|
|
|
|
|
'url',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
protected $casts = [
|
|
|
|
|
'has_thumb' => 'boolean',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
protected $appends = [
|
|
|
|
|
'full_src',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* URL کامل تصویر — همان قرارداد قبلی API (src = آدرس قابل نمایش).
|
|
|
|
|
*/
|
|
|
|
|
public function getSrcAttribute(): ?string
|
|
|
|
|
{
|
|
|
|
|
return self::publicUrl($this->attributes['src'] ?? null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getFullSrcAttribute(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->src;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function publicUrl(?string $path): ?string
|
|
|
|
|
{
|
|
|
|
|
if ($path === null || trim($path) === '') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$trimmed = trim($path);
|
|
|
|
|
|
|
|
|
|
if (preg_match('/^https?:\/\//i', $trimmed)) {
|
|
|
|
|
return self::rewriteWrongPublicUrl($trimmed) ?? $trimmed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$relative = self::normalizeRelativeStoragePath($trimmed);
|
|
|
|
|
$base = self::resolvePublicBaseUrl();
|
|
|
|
|
if ($relative === '') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if ($base === '') {
|
|
|
|
|
return $relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $base.'/'.$relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* مسیر نسبی داخل public/storage — حتی اگر قبلاً URL کامل اشتباه در DB ذخیره شده باشد.
|
|
|
|
|
*/
|
|
|
|
|
public static function normalizeRelativeStoragePath(string $path): string
|
|
|
|
|
{
|
|
|
|
|
$path = trim($path);
|
|
|
|
|
|
|
|
|
|
if (preg_match('#/storage/(.+)$#i', $path, $matches)) {
|
|
|
|
|
return 'storage/'.ltrim($matches[1], '/');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$relative = ltrim($path, '/');
|
|
|
|
|
|
|
|
|
|
if (! str_starts_with($relative, 'storage/')) {
|
|
|
|
|
$relative = 'storage/'.$relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* اصلاح URLهای قدیمی: https://site.ir/storage/... → https://site.ir/server/storage/...
|
|
|
|
|
*/
|
|
|
|
|
public static function rewriteWrongPublicUrl(string $url): ?string
|
|
|
|
|
{
|
|
|
|
|
if (! preg_match('#^(https?://[^/]+)/storage/(.+)$#i', $url, $matches)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$base = self::resolvePublicBaseUrl();
|
|
|
|
|
if ($base === '') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (str_contains($base, '/server') && ! str_contains($matches[1].'/storage/', '/server/storage/')) {
|
|
|
|
|
return $base.'/storage/'.$matches[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* پایهٔ 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$domain = config('app.domain');
|
|
|
|
|
if (is_string($domain) && $domain !== '' && ! str_contains($domain, 'localhost')) {
|
|
|
|
|
return self::ensureAbsoluteUrl(self::ensureServerPublicBase(rtrim($domain, '/')));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$appUrl = config('app.url');
|
|
|
|
|
if (is_string($appUrl) && $appUrl !== '' && ! str_contains($appUrl, 'localhost')) {
|
|
|
|
|
return self::ensureAbsoluteUrl(self::ensureServerPublicBase(rtrim($appUrl, '/')));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function baseUrlFromRequest(): string
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
if (app()->bound('request')) {
|
|
|
|
|
$request = request();
|
|
|
|
|
$host = $request?->getSchemeAndHttpHost();
|
|
|
|
|
if (is_string($host) && $host !== '' && ! str_contains($host, 'localhost')) {
|
|
|
|
|
return self::ensureAbsoluteUrl(self::ensureServerPublicBase($host));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (\Throwable) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function ensureAbsoluteUrl(string $base): string
|
|
|
|
|
{
|
|
|
|
|
$base = rtrim(trim($base), '/');
|
|
|
|
|
if ($base === '') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! preg_match('/^https?:\/\//i', $base)) {
|
|
|
|
|
$base = 'https://'.ltrim($base, '/');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function baseUrlFromSettings(): string
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$rows = Setting::query()
|
2026-07-21 13:25:18 +03:30
|
|
|
->whereIn('key', ['bace_url', 'server_url'])
|
2026-07-09 13:37:03 +03:30
|
|
|
->where('is_active', 1)
|
|
|
|
|
->pluck('value', 'key');
|
|
|
|
|
|
|
|
|
|
foreach (['server_url', 'bace_url'] as $key) {
|
|
|
|
|
$value = trim((string) ($rows[$key] ?? ''));
|
|
|
|
|
if ($value !== '') {
|
|
|
|
|
return rtrim($value, '/');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (\Throwable) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function ensureServerPublicBase(string $base): string
|
|
|
|
|
{
|
|
|
|
|
if (preg_match('#/(server|api)(/|$)#i', $base)) {
|
|
|
|
|
return rtrim($base, '/');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rtrim($base, '/').'/server';
|
|
|
|
|
}
|
|
|
|
|
}
|