join('documents as d', 'd.id', '=', 'r.document_id') ->where('r.published', true)->where('r.rights_status', 'authorized')->where('d.rights_status', 'authorized') ->where('d.status', 'active')->where('d.data_classification', 'public')->whereNull('d.deleted_at') ->where('d.malware_scan_status', 'clean')->whereColumn('d.sha256', 'd.malware_scanned_sha256'); } public function original(object $resource): ?array { $document = DB::table('documents')->find($resource->document_id); if (! $document || $document->status !== 'active' || $document->malware_scan_status !== 'clean' || ! in_array($document->media_type, ['text/plain', 'application/json'], true)) { return null; } $binary = app(VerifiedDocumentBinary::class)->respond($document); $bytes = $binary instanceof BinaryFileResponse ? file_get_contents($binary->getFile()->getPathname()) : $binary->getContent(); if (! is_string($bytes) || strlen($bytes) > 10485760) { return null; } $original = json_decode($bytes, true); return is_array($original) && ($original['format'] ?? '') === 'investmoz-legacy-article-v1' && is_string($original['content_html'] ?? null) ? $original : null; } public function plain(string $html): string { return trim(html_entity_decode(strip_tags(preg_replace('~|~i', "\n\n", $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8')); } public function content(object $resource, ?array $original): array { return $resource->editorial_content ? json_decode($resource->editorial_content, true, 512, JSON_THROW_ON_ERROR) : ['body' => $this->plain($original['content_html'] ?? ''), 'image_resource_id' => null, 'image_mode' => 'original', 'preserve_original_html' => true]; } private function sourceUrl(mixed $url, ?string $base = null): ?string { if (! is_string($url) || trim($url) === '') { return null; } try { $absolute = $base ? (string) UriResolver::resolve(new Uri($base), new Uri($url)) : $url; return filter_var($absolute, FILTER_VALIDATE_URL) && in_array(strtolower((string) parse_url($absolute, PHP_URL_SCHEME)), ['http', 'https'], true) ? $absolute : null; } catch (\InvalidArgumentException) { return null; } } public function reading(object $resource, ?array $override = null, bool $private = false): array { $original = $this->original($resource); $content = $override ?? $this->content($resource, $original); abort_unless(trim($content['body'] ?? '') !== '', 404, 'Este documento não contém um artigo legível.'); $document = DB::table('documents')->find($resource->document_id); // The original is verified above; ordinary text adaptations retain the binary gate too. app(VerifiedDocumentBinary::class)->respond($document); $mode = $content['image_mode'] ?? (! empty($content['image_resource_id']) ? 'selected' : 'none'); $originalImageUrl = $this->sourceUrl($original['image_source_url'] ?? null, $original['source_url'] ?? null); $expectedImageSha = $original['image_sha256'] ?? null; if (! $expectedImageSha && $originalImageUrl === 'https://investemoz.tempo.co.mz/images/tempo-background.png') { foreach (json_decode(file_get_contents(config_path('legacy_editorial_resources.json')), true, 512, JSON_THROW_ON_ERROR) as $accepted) { if ($accepted['sha256'] === $document->sha256) { $expectedImageSha = $accepted['original_image_sha256'] ?? null; break; } } } $images = $this->publicResources()->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp']); $image = null; if ($mode === 'selected' && ! empty($content['image_resource_id'])) { $image = (clone $images)->where('r.id', $content['image_resource_id'])->first(['r.id', 'r.title']); } if ($mode === 'original' && $original) { $image = (clone $images)->where(function ($q) use ($expectedImageSha, $originalImageUrl) { $q->whereRaw('1=0'); if ($expectedImageSha) { $q->orWhere('d.sha256', $expectedImageSha); } elseif ($originalImageUrl) { $q->orWhere('r.source_url', $originalImageUrl)->orWhere('d.original_source', $originalImageUrl); } })->first(['r.id', 'r.title']); } if ($image) { // A public flag alone never licenses missing or changed image bytes. $binary = (clone $images)->where('r.id', $image->id)->first(['d.*']); try { app(VerifiedDocumentBinary::class)->respond($binary); } catch (HttpException) { $image = null; } } $originalFormat = $original && ($content['preserve_original_html'] ?? false) && $content['body'] === $this->plain($original['content_html']); $html = $originalFormat ? app(LegacyContent::class)->sanitize($original['content_html'], true) : null; $imageReferences = []; if ($original) { preg_match_all('~]*\bsrc\s*=\s*([\'"])(.*?)\1~is', $original['content_html'], $matches); foreach ($matches[2] as $source) { $url = $this->sourceUrl(html_entity_decode($source, ENT_QUOTES | ENT_HTML5, 'UTF-8'), $original['source_url'] ?? null); if ($url) { $imageReferences[] = $url; } } } return ['id' => $resource->id, 'title' => $resource->title, 'description' => $resource->description, 'author' => $document->issuer, 'body' => $content['body'], 'html' => $html, 'image_url' => $image ? '/documentos/'.$image->id.'/view' : null, 'image_notice' => $mode === 'selected' && ! $image ? 'A imagem escolhida está indisponível para divulgação; a selecção mantém-se na edição.' : ($mode === 'original' && ! $image && ! empty($original['image_source_url']) ? 'Imagem de origem referenciada, mas sem exemplar público verificado na biblioteca. A referência original mantém-se preservada.' : null), 'source_url' => $this->sourceUrl($original['source_url'] ?? $resource->source_url), 'published_at' => $original['published_at'] ?? null, 'modified_at' => $original['modified_at'] ?? null, 'legacy_id' => $original['legacy_id'] ?? null, 'image_source_url' => $originalImageUrl, 'image_expected_sha256' => $expectedImageSha, 'image_references' => array_values(array_unique($imageReferences)), 'metadata_version' => $resource->metadata_version, 'private_preview' => $private, 'original_url' => $private ? '/management/documents/'.$resource->document_id.'/download' : '/documentos/'.$resource->id, ]; } }