192 lines
7.1 KiB
PHP
192 lines
7.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
if (PHP_SAPI !== 'cli') {
|
|
http_response_code(404);
|
|
exit;
|
|
}
|
|
|
|
require_once __DIR__ . '/app.php';
|
|
require_once __DIR__ . '/zefixAPI.php';
|
|
require_once __DIR__ . '/emailSender.php';
|
|
|
|
$maxExecutionTime = app_env_int('TASK_EXECUTOR_MAX_SECONDS', 120, 30, 900);
|
|
$minimumTaskAge = app_env_int('TASK_MIN_AGE_SECONDS', 10, 0, 300);
|
|
$latestEndTime = time() + $maxExecutionTime;
|
|
$smtpPassword = app_env('smtppassword');
|
|
|
|
app_migrate_legacy_tasks();
|
|
$downloadRetention = app_env_int('DOWNLOAD_RETENTION_HOURS', 48, 1, 720) * 3600;
|
|
$staleTaskRetention = app_env_int('STALE_TASK_RETENTION_HOURS', 168, 24, 2160) * 3600;
|
|
app_cleanup_directory(app_download_dir(), $downloadRetention, 'csv');
|
|
app_cleanup_directory(app_task_dir(), $staleTaskRetention, 'json');
|
|
app_cleanup_directory(__DIR__ . DIRECTORY_SEPARATOR . 'download', $downloadRetention, 'csv');
|
|
|
|
$executorLock = fopen(app_private_dir() . DIRECTORY_SEPARATOR . 'executor.lock', 'c+');
|
|
if ($executorLock === false || !flock($executorLock, LOCK_EX | LOCK_NB)) {
|
|
fwrite(STDOUT, "Another executor is already running.\n");
|
|
exit(0);
|
|
}
|
|
|
|
/**
|
|
* @return int|false|null Next offset, false when complete, null on a temporary error.
|
|
*/
|
|
function doRequest(array $data, string $filename, string $username, string $password): int|false|null
|
|
{
|
|
$response = sendAPICompanySearchRequest($username, $password, $data);
|
|
if (!is_string($response) || $response === '') {
|
|
return null;
|
|
}
|
|
|
|
$responseObject = json_decode($response, true);
|
|
if (!is_array($responseObject) || array_key_exists('error', $responseObject) || !is_array($responseObject['list'] ?? null)) {
|
|
error_log('ZEFIX search returned an invalid or error response.');
|
|
return null;
|
|
}
|
|
|
|
$companyData = [];
|
|
foreach ($responseObject['list'] as $company) {
|
|
if (!is_array($company) || !isset($company['uid'])) {
|
|
continue;
|
|
}
|
|
|
|
$detailsResponse = sendAPICompanyInfoRequest($username, $password, (string)$company['uid']);
|
|
$details = is_string($detailsResponse) ? json_decode($detailsResponse, true) : null;
|
|
$companyFullData = is_array($details) && is_array($details[0] ?? null) ? $details[0] : null;
|
|
if ($companyFullData === null) {
|
|
continue;
|
|
}
|
|
|
|
$companyData[] = [
|
|
$companyFullData['name'] ?? '',
|
|
$companyFullData['address']['careOf'] ?? '',
|
|
$companyFullData['address']['street'] ?? '',
|
|
$companyFullData['address']['houseNumber'] ?? '',
|
|
$companyFullData['address']['swissZipCode'] ?? '',
|
|
$companyFullData['address']['city'] ?? '',
|
|
$companyFullData['uid'] ?? '',
|
|
$companyFullData['legalSeat'] ?? '',
|
|
$companyFullData['legalForm']['name']['de'] ?? '',
|
|
$companyFullData['status'] ?? '',
|
|
$companyFullData['sogcDate'] ?? '',
|
|
$companyFullData['deletionDate'] ?? '',
|
|
];
|
|
}
|
|
|
|
$isNewFile = !is_file($filename);
|
|
$file = fopen($filename, $isNewFile ? 'x' : 'a');
|
|
if ($file === false) {
|
|
error_log('Export file could not be opened.');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
if ($isNewFile) {
|
|
fputcsv($file, ['name', 'careOf', 'street', 'houseNumber', 'swissZipCode', 'city', 'uid', 'legalSeat', 'legalForm', 'status', 'sogcDate', 'deletionDate'], ',', '"', '\\');
|
|
@chmod($filename, 0600);
|
|
}
|
|
foreach ($companyData as $row) {
|
|
fputcsv($file, $row, ',', '"', '\\');
|
|
}
|
|
} finally {
|
|
fclose($file);
|
|
}
|
|
|
|
return ($responseObject['hasMoreResults'] ?? false)
|
|
? (int)($responseObject['maxOffset'] ?? 0)
|
|
: false;
|
|
}
|
|
|
|
function saveTask(string $filename, array $task): bool
|
|
{
|
|
$temporary = $filename . '.tmp';
|
|
$written = file_put_contents($temporary, json_encode($task, JSON_THROW_ON_ERROR), LOCK_EX);
|
|
if ($written === false || !rename($temporary, $filename)) {
|
|
@unlink($temporary);
|
|
return false;
|
|
}
|
|
@chmod($filename, 0600);
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
while ($latestEndTime - time() > 30) {
|
|
$taskFiles = glob(app_task_dir() . DIRECTORY_SEPARATOR . '*.json') ?: [];
|
|
sort($taskFiles, SORT_STRING);
|
|
|
|
if ($taskFiles === []) {
|
|
fwrite(STDOUT, "Nothing to do.\n");
|
|
break;
|
|
}
|
|
|
|
$taskFilename = $taskFiles[0];
|
|
$modified = filemtime($taskFilename);
|
|
if ($modified !== false && $modified + $minimumTaskAge > time()) {
|
|
sleep(min(5, max(1, ($modified + $minimumTaskAge) - time())));
|
|
continue;
|
|
}
|
|
|
|
$task = json_decode((string)file_get_contents($taskFilename), true);
|
|
if (!is_array($task) || !is_array($task['requests'] ?? null) || !filter_var($task['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
|
|
error_log('Invalid task quarantined: ' . basename($taskFilename));
|
|
@rename($taskFilename, $taskFilename . '.invalid');
|
|
continue;
|
|
}
|
|
|
|
if (!preg_match('/^[a-f0-9]{32}$/', (string)($task['downloadToken'] ?? ''))) {
|
|
$task['downloadToken'] = bin2hex(random_bytes(16));
|
|
|
|
// Preserve partially generated exports from the previous public-download layout.
|
|
$legacyCsv = __DIR__ . DIRECTORY_SEPARATOR . 'download' . DIRECTORY_SEPARATOR . pathinfo($taskFilename, PATHINFO_FILENAME) . '.csv';
|
|
if (is_file($legacyCsv)) {
|
|
@rename($legacyCsv, app_download_path($task['downloadToken']));
|
|
}
|
|
if (!saveTask($taskFilename, $task)) {
|
|
error_log('Could not migrate legacy task metadata.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
$csvFile = app_download_path((string)$task['downloadToken']);
|
|
if ($task['requests'] !== []) {
|
|
$nextOffset = doRequest($task['requests'][0], $csvFile, (string)$username, (string)$password);
|
|
if ($nextOffset === null) {
|
|
fwrite(STDOUT, "Temporary API error; task retained for retry.\n");
|
|
break;
|
|
}
|
|
|
|
if ($nextOffset !== false) {
|
|
$task['requests'][0]['offset'] = $nextOffset;
|
|
} else {
|
|
array_shift($task['requests']);
|
|
}
|
|
|
|
if (!saveTask($taskFilename, $task)) {
|
|
error_log('Could not save task progress.');
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!is_file($csvFile)) {
|
|
$emptyFile = fopen($csvFile, 'x');
|
|
if ($emptyFile !== false) {
|
|
fputcsv($emptyFile, ['name', 'careOf', 'street', 'houseNumber', 'swissZipCode', 'city', 'uid', 'legalSeat', 'legalForm', 'status', 'sogcDate', 'deletionDate'], ',', '"', '\\');
|
|
fclose($emptyFile);
|
|
@chmod($csvFile, 0600);
|
|
}
|
|
}
|
|
|
|
if (sendEmail((string)$task['email'], (string)$task['downloadToken'], $smtpPassword)) {
|
|
unlink($taskFilename);
|
|
fwrite(STDOUT, 'Completed ' . basename($taskFilename) . ".\n");
|
|
} else {
|
|
fwrite(STDOUT, "Email delivery failed; task retained for retry.\n");
|
|
break;
|
|
}
|
|
}
|
|
} finally {
|
|
flock($executorLock, LOCK_UN);
|
|
fclose($executorLock);
|
|
}
|