where('user_id', $user->id) ->where('is_root_storage', true) ->where('status', '!=', Transfer::STATUS_DELETED) ->first(); if ($existing) { return $existing; } $billingPeriodDays = (int) config('transfer.billing_period_days', 30); return Transfer::create([ 'user_id' => $user->id, 'title' => 'My files', 'status' => Transfer::STATUS_ACTIVE, 'is_root_storage' => true, 'retention_days' => $billingPeriodDays, ]); } public function createFolder(User $user, string $name, ?Transfer $parent = null): Transfer { $title = trim($name); if ($title === '') { throw new RuntimeException('Enter a folder name.'); } $billingPeriodDays = (int) config('transfer.billing_period_days', 30); return Transfer::create([ 'user_id' => $user->id, 'title' => $title, 'status' => Transfer::STATUS_ACTIVE, 'is_folder' => true, 'retention_days' => $billingPeriodDays, ]); } public function uploadDestination(User $user, ?Transfer $folder): Transfer { if ($folder !== null) { abort_unless($folder->user_id === $user->id && $folder->is_folder, 403); return $folder; } return $this->rootStorage($user); } /** @param list $filenames */ public function findConflicts(User $user, ?Transfer $folder, array $filenames): array { $conflicts = []; foreach ($filenames as $filename) { $name = trim((string) $filename); if ($name === '') { continue; } $existing = $this->findByName($user, $folder, $name); if ($existing) { $conflicts[] = [ 'filename' => $name, 'existing_id' => $existing->id, ]; } } return $conflicts; } /** * @param list $files * @param array $resolutions * @return list */ public function uploadFiles(User $user, ?Transfer $folder, array $files, array $resolutions = []): array { if ($files === []) { throw new RuntimeException('Choose at least one file to upload.'); } $destination = $this->uploadDestination($user, $folder); $maxFiles = (int) config('transfer.max_files_per_transfer', 20); if (count($files) > $maxFiles) { throw new RuntimeException("You can upload up to {$maxFiles} files at a time."); } $maxBytes = (int) config('transfer.max_file_bytes', 0); $uploaded = []; DB::transaction(function () use ($user, $destination, $files, $resolutions, $maxBytes, &$uploaded) { $destination = Transfer::query()->lockForUpdate()->findOrFail($destination->id); $wasEmpty = $destination->files()->count() === 0; foreach ($files as $file) { if (! $file instanceof UploadedFile) { continue; } if ($maxBytes > 0 && $file->getSize() > $maxBytes) { throw new RuntimeException($file->getClientOriginalName().' exceeds the maximum file size.'); } $originalName = $file->getClientOriginalName() ?: 'file'; $resolution = $resolutions[$originalName] ?? null; if ($resolution === 'replace') { $existing = $this->findByName($user, $destination->is_folder ? $destination : null, $originalName); if ($existing) { $this->transfers->deleteFile($existing); } $storedName = $originalName; } elseif ($resolution === 'keep_both') { $storedName = $this->uniqueFilename($user, $destination->is_folder ? $destination : null, $originalName); } else { $existing = $this->findByName($user, $destination->is_folder ? $destination : null, $originalName); if ($existing) { throw new RuntimeException("A file named \"{$originalName}\" already exists. Choose replace or keep both."); } $storedName = $originalName; } $stored = $this->transfers->addStorageFile($user, $destination, $file, $storedName); $uploaded[] = $stored; } if ($uploaded === []) { throw new RuntimeException('No valid files were uploaded.'); } if ($wasEmpty && ! $destination->qr_code_id) { app(TransferBillingService::class)->chargeInitialPeriod($destination->fresh(), $user); } }); return $uploaded; } public function findByName(User $user, ?Transfer $folder, string $filename): ?TransferFile { return TransferFile::query() ->where('original_name', $filename) ->whereHas('transfer', function ($query) use ($user, $folder) { $query->where('user_id', $user->id) ->where('status', '!=', Transfer::STATUS_DELETED); if ($folder !== null) { $query->whereKey($folder->id); } else { $query->where('is_folder', false); } }) ->first(); } public function uniqueFilename(User $user, ?Transfer $folder, string $filename): string { $base = pathinfo($filename, PATHINFO_FILENAME); $ext = pathinfo($filename, PATHINFO_EXTENSION); $candidate = $filename; $counter = 1; while ($this->findByName($user, $folder, $candidate)) { $suffix = ' ('.$counter.')'; $candidate = $base.$suffix.($ext !== '' ? '.'.$ext : ''); $counter++; } return $candidate; } }