scope !== null && $indexes->dossier !== null; } /** @param array $definition * @return array */ public function field(array $definition, string $key): array { $field = $definition['fields'][$key] ?? null; if (! $field || $field['type'] !== 'attachment') { throw ValidationException::withMessages(['field_key' => 'Seleccione uma pergunta de anexo da versão deste pedido.']); } return $field; } /** @param array $field */ public function checkMetadata(\stdClass $document, array $field, string $path, bool $allowUnknown = false): void { $mime = strtolower(trim(explode(';', (string) ($document->media_type ?? ''), 2)[0])); if (($mime === '' && ! $allowUnknown) || ($mime !== '' && ! in_array($mime, $field['mime_types'], true)) || ($document->size_bytes === null && ! $allowUnknown) || ($document->size_bytes !== null && ($document->size_bytes === false || $document->size_bytes < 0 || $document->size_bytes > $field['max_size_kb'] * 1024))) { throw ValidationException::withMessages([$path => 'O ficheiro não corresponde aos formatos ou ao tamanho máximo desta pergunta. Escolha outro suporte; o original é conservado.']); } } /** Validate references through the existing case/request boundary; no parallel document store. * @param array $payload * @param array $definition * @return array */ public function snapshot(\stdClass $request, array $payload, array $definition, bool $submitting): array { $applicable = app(VirtualDesk::class)->applicableFields($payload, $definition); $snapshot = []; $verified = []; foreach ($definition['fields'] as $key => $field) { if ($field['type'] !== 'attachment') { continue; } $files = []; $seen = []; foreach ($payload[$key] ?? [] as $linkId) { $document = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id') ->where('l.id', $linkId)->where('l.case_id', $request->case_id)->whereNull('d.deleted_at') ->where(fn ($q) => $q->whereNull('l.request_id')->orWhere('l.request_id', $request->id)) ->first(['d.*', 'l.id as link_id', 'l.source_reference', 'l.category', 'l.valid_until as declared_valid_until', 'l.stage as linked_stage']); if (! $document) { throw ValidationException::withMessages(['payload.'.$key => 'Seleccione documentos associados a este dossier e disponíveis neste pedido.']); } if (isset($seen[$document->id])) { throw ValidationException::withMessages(['payload.'.$key => 'Seleccione cada ficheiro apenas uma vez nesta pergunta. Pode reutilizá-lo noutras perguntas aplicáveis.']); } $seen[$document->id] = true; $active = isset($applicable[$key]); // Hidden answers are preserved; they are not accepted as applicable evidence. if ($active) { $this->checkMetadata($document, $field, 'payload.'.$key, true); } $usable = $document->status === 'active' && $document->malware_scan_status === 'clean' && hash_equals(trim($document->sha256), trim((string) $document->malware_scanned_sha256)); if ($submitting && $active) { if (! $usable) { throw ValidationException::withMessages(['payload.'.$key => 'O suporte seleccionado ainda não está disponível com análise de segurança válida. Actualize o estado dos documentos antes de submeter.']); } if (! isset($verified[$document->id])) { try { $verificationDocument = clone $document; $verificationDocument->media_type = $verificationDocument->media_type ?: 'application/octet-stream'; $response = app(VerifiedDocumentBinary::class)->respond($verificationDocument); if ($response instanceof BinaryFileResponse) { $verified[$document->id] = ['size_bytes' => $response->getFile()->getSize(), 'media_type' => (new \finfo(FILEINFO_MIME_TYPE))->file($response->getFile()->getPathname())]; } else { $content = $response->getContent(); if (! is_string($content)) { throw ValidationException::withMessages(['payload.'.$key => 'Não foi possível conferir o original deste suporte.']); } $verified[$document->id] = ['size_bytes' => strlen($content), 'media_type' => (new \finfo(FILEINFO_MIME_TYPE))->buffer($content)]; } } catch (HttpExceptionInterface $error) { throw ValidationException::withMessages(['payload.'.$key => 'O suporte não está disponível com a autorização e integridade exigidas. Recupere ou seleccione outro documento.']); } } $this->checkMetadata((object) $verified[$document->id], $field, 'payload.'.$key); $recordedMime = strtolower(trim(explode(';', (string) ($document->media_type ?? ''), 2)[0])); if (($document->size_bytes !== null && $verified[$document->id]['size_bytes'] !== (int) $document->size_bytes) || ($recordedMime !== '' && $verified[$document->id]['media_type'] !== $recordedMime)) { throw ValidationException::withMessages(['payload.'.$key => 'O formato ou tamanho do original diverge dos dados registados. Recupere ou seleccione outro documento.']); } } $measured = $submitting && $active ? $verified[$document->id] : null; $files[] = ['link_id' => $document->link_id, 'document_id' => $document->id, 'file_name' => $document->file_name, 'sha256' => trim($document->sha256), 'version_no' => $document->version_no, 'media_type' => $measured['media_type'] ?? $document->media_type, 'size_bytes' => $measured['size_bytes'] ?? $document->size_bytes, 'recorded_media_type' => $document->media_type, 'recorded_size_bytes' => $document->size_bytes, 'metadata_measured' => $measured !== null, 'source_reference' => $document->source_reference, 'valid_until' => $document->declared_valid_until, 'stage' => $document->linked_stage, 'status' => $document->status, 'malware_scan_status' => $document->malware_scan_status, 'usable_at_submission' => $submitting && $active && $usable]; } $snapshot[$key] = ['label' => $field['label'], 'applicable' => isset($applicable[$key]), 'service_version_id' => $request->service_version_id, 'limits' => ['mime_types' => $field['mime_types'], 'max_size_kb' => $field['max_size_kb'], 'max_files' => $field['max_files']], 'files' => $files]; } return $snapshot; } }