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

  1. 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
  2. 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
  3. 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

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.