118 lines
8.6 KiB
PHP
118 lines
8.6 KiB
PHP
<?php
|
|
|
|
// Read-only export from the named QA; this does not import or publish in a destination.
|
|
require dirname(__DIR__).'/vendor/autoload.php';
|
|
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
|
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
use App\Services\EditorialReading;
|
|
use App\Services\VerifiedDocumentBinary;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
|
|
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
|
if (! app()->environment('testing') || $target->name !== 'simulator_fiscal_delta_20261021_test' || (int) $target->port !== 55441) {
|
|
throw new RuntimeException('Named isolated QA only');
|
|
}
|
|
$directory = base_path('outputs/legacy-editorial-20261022');
|
|
if (! is_dir($directory)) {
|
|
mkdir($directory, 0700, true);
|
|
}
|
|
$identities = json_decode(file_get_contents(config_path('legacy_editorial_resources.json')), true, 512, JSON_THROW_ON_ERROR);
|
|
if (count($identities) !== 28) {
|
|
throw new RuntimeException('Exact 28-original scope required');
|
|
}
|
|
$label = $argv[1] ?? 'v1';
|
|
if (! preg_match('/^[a-z0-9-]{1,32}$/D', $label)) {
|
|
throw new RuntimeException('Invalid package label');
|
|
}
|
|
$zip = new ZipArchive;
|
|
$archive = $directory.'/legacy-editorial-private-'.$label.'.zip';
|
|
if (is_file($archive)) {
|
|
throw new RuntimeException('Preserve existing package; choose a reviewed subsequent version rather than overwrite');
|
|
}
|
|
if ($zip->open($archive, ZipArchive::CREATE | ZipArchive::EXCL) !== true) {
|
|
throw new RuntimeException('Cannot create private package');
|
|
}
|
|
$manifest = ['format' => 'investmoz-legacy-editorial-private-delivery-v1', 'prepared_at' => gmdate(DATE_ATOM), 'destination' => 'teste-investidor.tempo.co.mz', 'publish' => false, 'documents' => [], 'images' => [],
|
|
'rules' => ['Transport/import is private and distinct from publication.', 'Resolve target IDs by original SHA256/external identity; no QA user/document/resource IDs are authoritative in the destination.', 'Keep source QA revisions as provenance, never impersonate their actors as destination audit authors.', 'Recheck target scan and existing identity/version; reuse matching originals, never overwrite conflicts.', 'Apply current metadata/edition through authenticated management, creating destination history. No automatic publication.']];
|
|
$reading = app(EditorialReading::class);
|
|
$rows = [];
|
|
$imageHashes = [];
|
|
DB::beginTransaction();
|
|
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
try {
|
|
foreach ($identities as $identity) {
|
|
$doc = DB::table('documents')->where('external_id', $identity['external_id'])->where('sha256', $identity['sha256'])->whereNull('deleted_at')->first();
|
|
if (! $doc) {
|
|
throw new RuntimeException('Missing original identity');
|
|
}
|
|
$resource = DB::table('portal_resources')->where('document_id', $doc->id)->first();
|
|
if (! $resource || $resource->published || $doc->data_classification !== 'internal') {
|
|
throw new RuntimeException('Expected private QA draft');
|
|
}
|
|
$binary = app(VerifiedDocumentBinary::class)->respond($doc);
|
|
$bytes = $binary instanceof BinaryFileResponse ? file_get_contents($binary->getFile()->getPathname()) : $binary->getContent();
|
|
if (hash('sha256', $bytes) !== $identity['sha256']) {
|
|
throw new RuntimeException('Original changed');
|
|
}
|
|
$file = 'originals/'.$identity['external_id'].'.txt';
|
|
$zip->addFromString($file, $bytes);
|
|
$display = $reading->reading($resource, null, true);
|
|
$edition = $resource->editorial_content ? json_decode($resource->editorial_content, true, 512, JSON_THROW_ON_ERROR) : null;
|
|
$imageBinding = null;
|
|
if ($edition && ! empty($edition['image_resource_id'])) {
|
|
$imageDoc = DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->where('r.id', $edition['image_resource_id'])->first(['d.*']);
|
|
if (! $imageDoc) {
|
|
throw new RuntimeException('Selected image missing');
|
|
}
|
|
$imageBinding = ['sha256' => $imageDoc->sha256, 'source_url' => $imageDoc->original_source];
|
|
unset($edition['image_resource_id']);
|
|
}
|
|
if ($display['image_url']) {
|
|
preg_match('~/documentos/(\d+)/view~', $display['image_url'], $match);
|
|
$imageDoc = $reading->publicResources()->where('r.id', (int) $match[1])->first(['d.*']);
|
|
$imageBinary = app(VerifiedDocumentBinary::class)->respond($imageDoc);
|
|
$imageBytes = $imageBinary instanceof BinaryFileResponse ? file_get_contents($imageBinary->getFile()->getPathname()) : $imageBinary->getContent();
|
|
if (! isset($imageHashes[$imageDoc->sha256])) {
|
|
$imageFile = 'images/'.$imageDoc->sha256;
|
|
$zip->addFromString($imageFile, $imageBytes);
|
|
$imageHashes[$imageDoc->sha256] = true;
|
|
$manifest['images'][] = ['file' => $imageFile, 'sha256' => $imageDoc->sha256, 'source_url' => $imageDoc->original_source, 'mode' => 'reuse target matching hash first; import only if absent and authorized'];
|
|
}
|
|
}
|
|
$revisions = DB::table('portal_resource_revisions')->where('resource_id', $resource->id)->orderBy('revision_no')->get();
|
|
$historyFile = 'provenance/'.$identity['external_id'].'.json';
|
|
$zip->addFromString($historyFile, json_encode(['scope' => 'QA provenance only; IDs and actors are not destination identities', 'resource_snapshot' => $resource, 'revisions' => $revisions], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
$metadata = (array) $resource;
|
|
foreach (['id', 'document_id', 'created_by', 'approved_by', 'last_reviewed_by', 'last_reviewed_at', 'metadata_version', 'document_group_id', 'duplicate_of_resource_id', 'document_identity_key', 'editorial_content', 'created_at', 'updated_at'] as $key) {
|
|
unset($metadata[$key]);
|
|
}
|
|
$manifest['documents'][] = ['external_id' => $identity['external_id'], 'file' => $file, 'sha256' => $identity['sha256'], 'kind' => $identity['kind'], 'metadata' => $metadata, 'qa_metadata_version' => $resource->metadata_version, 'edition' => $edition, 'selected_image' => $imageBinding, 'original_image_reference' => $display['image_source_url'], 'original_image_sha256' => $display['image_expected_sha256'], 'body_image_references' => $display['image_references'], 'history_file' => $historyFile, 'publish' => false, 'version_identity' => 'Legado '.$identity['external_id']];
|
|
$rows[] = ['external_id' => $identity['external_id'], 'qa_resource_id' => $resource->id, 'version' => $resource->metadata_version, 'original_sha256' => $doc->sha256, 'private' => ! $resource->published, 'image_available' => (bool) $display['image_url'], 'image_reference' => $display['image_source_url'], 'body_image_references' => count($display['image_references']), 'history_revisions' => $revisions->count()];
|
|
}
|
|
} finally {
|
|
DB::rollBack();
|
|
}
|
|
$manifest['notice'] = 'The QA proof edition of original post 62413 contains an explicitly marked QA note. Review/correct it in the portal before any publication; no editorial text is silently removed from this package.';
|
|
$zip->addFromString('manifest.json', json_encode($manifest, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
$zip->close();
|
|
$verify = new ZipArchive;
|
|
if ($verify->open($archive, ZipArchive::CHECKCONS) !== true) {
|
|
throw new RuntimeException('Archive invalid');
|
|
}
|
|
foreach ($manifest['documents'] as $item) {
|
|
if (hash('sha256', $verify->getFromName($item['file'])) !== $item['sha256']) {
|
|
throw new RuntimeException('Archive original mismatch');
|
|
}
|
|
}
|
|
foreach ($manifest['images'] as $item) {
|
|
if (hash('sha256', $verify->getFromName($item['file'])) !== $item['sha256']) {
|
|
throw new RuntimeException('Archive image mismatch');
|
|
}
|
|
}
|
|
$entries = $verify->numFiles;
|
|
$verify->close();
|
|
$receipt = ['qa_only' => true, 'originals' => count($rows), 'rows' => $rows, 'archive_entries' => $entries, 'archive_sha256' => hash_file('sha256', $archive), 'archive_bytes' => filesize($archive), 'verified_original_hashes' => 28, 'included_image_originals' => count($manifest['images']), 'destination_imported' => false, 'destination_published' => false, 'database_writes' => 0];
|
|
file_put_contents($directory.'/private-delivery-'.$label.'-receipt.json', json_encode($receipt, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode(['originals' => count($rows), 'archive_entries' => $entries, 'sha256' => $receipt['archive_sha256'], 'included_images' => count($manifest['images']), 'destination_imported' => false, 'destination_published' => false]);
|