Leantime/leantime · error · Leantime\Core\Files\Exceptions\FileValidationException
1099
1099
Error message
Missing module or moduleId
What it means
Files::upload() validates that both $module and $moduleId are non-empty before doing anything. On failure it throws FileValidationException (code 1099, VALIDATION_ERROR) — but note the throw is inside a try whose catch (FileValidationException) immediately returns $e->getUserMessage(), so the caller does NOT see an exception: upload() returns the error text as a string. The declared return type array|string|false means a string return signals a validation failure.
Source
Thrown at app/Domain/Files/Services/Files.php:108
return $this->fileRepository->getFilesByModule($module, $entityId, $userId);
}
/**
* @throws BindingResolutionException
*
* @api
*/
public function upload($file, $module, $moduleId, $entity = null, $disk = 'default'): array|string|false
{
try {
// Validate input parameters
if (empty($module) || empty($moduleId)) {
Log::warning('Upload attempted with missing module or moduleId', [
'module' => $module,
'moduleId' => $moduleId,
]);
throw new FileValidationException('Missing module or moduleId', FileValidationException::VALIDATION_ERROR);
}
if (! isset($file['file']) || ! is_array($file['file'])) {
throw new FileNotFoundException('File not included in request or has invalid format');
}
} catch (FileValidationException $e) {
Log::warning('File validation failed: '.$e->getMessage());
return $e->getUserMessage();
}
// Normalize module names for consistency
if ($module === 'projects') {
$module = 'project';
}
if ($module === 'tickets') {
$module = 'ticket';
}View on GitHub (pinned to 9a9f49f100)
Solutions
- Pass a non-empty module ('ticket', 'project', etc. — plural forms are normalized) and a non-zero moduleId; save the parent entity first and upload after
- Check the return: if is_string($result) the upload failed validation and the string is the user-facing message — handle it instead of treating it as success data
- Make the UI disable the file input until module/entity context is available
Example fix
// before
$result = $filesService->upload($_FILES, $module, $moduleId);
return $result; // callers assume array
// after
if (! empty($module) && ! empty($moduleId)) {
$result = $filesService->upload($_FILES, $module, $moduleId);
return is_string($result) ? ['error' => $result] : $result;
}
return ['error' => 'Missing module or moduleId']; Defensive patterns
Strategy: validation
Validate before calling
if (empty($module) || empty($moduleId)) {
// upload() will return a string error; catch it before the call
return ['error' => 'module and moduleId are required for uploads'];
}
$result = $filesService->upload($file, $module, $moduleId);
if (is_string($result)) {
return ['error' => $result]; // validation failed server-side
} Type guard
/** A string return from upload() means a validation error; array means success. */
function isUploadSuccess(mixed $result): bool
{
return is_array($result);
} Prevention
- Remember upload() reports FileValidationException failures as a returned STRING, not a thrown exception — always branch on is_string($result)
- Save the parent ticket/project before attaching files so moduleId is a real non-zero id
- Treat module '0'/0 and moduleId 0/0/'0' as missing — empty() does
When it happens
Trigger: Calling upload($file, null, 42) or upload($file, 'ticket', 0); a JS upload widget that appends the module fields to FormData only after the entity exists, so new/unsaved entities send empty values; param name mismatches between caller and the (module, moduleId) signature.
Common situations: Uploading an attachment before saving the parent ticket/project (id still 0); refactoring the upload form and dropping hidden module/moduleId inputs; empty($moduleId) also catching '0' and 0, so id 0 is treated as missing.
Related errors
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/a97e2de658fba997.
Report an issue: GitHub.