Merge branch 'latest' of forge.dmz.skyfritt.net:stopplidelsen/innhold into latest

This commit is contained in:
Ruben Solvang 2026-02-01 22:08:49 +01:00
commit 80f308d186

View file

@ -407,7 +407,9 @@ function showMenu(): void {
echo " 3) Send e-post pa nytt til mislykkede\n"; echo " 3) Send e-post pa nytt til mislykkede\n";
echo " 4) Send bekreftelse pa nytt til ubekreftede\n"; echo " 4) Send bekreftelse pa nytt til ubekreftede\n";
echo " 5) Marker oppforinger som ignorert\n"; echo " 5) Marker oppforinger som ignorert\n";
echo " 6) Avslutt\n"; echo " 6) Manuell bekreftelse av signaturer\n";
echo " 7) Slett signaturer\n";
echo " 8) Avslutt\n";
echo "\n"; echo "\n";
} }
@ -642,6 +644,313 @@ function resendToUnconfirmed(): void {
printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green'); printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
} }
/**
* Atomically modify petition CSV file with a callback function.
* The callback receives the rows array (without header) by reference and can modify it.
* Returns the result from the callback, or null on file errors.
*
* @param string $petitionId The petition ID
* @param callable $callback Function that receives (&$rows, $petitionId) and returns a result
* @return mixed|null The callback's return value, or null on error
*/
function modifyPetitionFile(string $petitionId, callable $callback): mixed {
$csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
if (!file_exists($csvPath)) {
return null;
}
// Open with r+ to allow reading and writing, acquire exclusive lock immediately
// This matches the pattern used in petition-form.php to prevent race conditions
$fp = fopen($csvPath, 'r+');
if (!$fp) {
printColor(" Advarsel: Kunne ikke apne {$petitionId}.csv\n", 'yellow');
return null;
}
if (!flock($fp, LOCK_EX)) {
fclose($fp);
printColor(" Advarsel: Kunne ikke lase {$petitionId}.csv\n", 'yellow');
return null;
}
// Read all rows while holding the lock
$header = fgetcsv($fp, null, ',', '"', '');
if ($header === false) {
flock($fp, LOCK_UN);
fclose($fp);
return null;
}
$rows = [];
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
$rows[] = $row;
}
// Call the callback to modify rows
$originalCount = count($rows);
$result = $callback($rows, $petitionId);
// Write back if rows were modified (count changed or callback signals modification)
$modified = (count($rows) !== $originalCount);
if (!$modified && is_array($result) && isset($result['modified'])) {
$modified = $result['modified'];
}
if ($modified) {
rewind($fp);
ftruncate($fp, 0);
fputcsv($fp, $header, ',', '"', '');
foreach ($rows as $row) {
fputcsv($fp, $row, ',', '"', '');
}
}
flock($fp, LOCK_UN);
fclose($fp);
return $result;
}
/**
* Manually confirm signatures by email address
*/
function manuallyConfirmSignatures(): void {
echo "\n";
printColor("Manuell bekreftelse av signaturer\n", 'cyan');
echo "\n";
$input = prompt("Skriv inn e-postadresse(r) (kommaseparert): ");
if (empty(trim($input))) {
echo "Ingen e-postadresser oppgitt.\n";
return;
}
// Parse and clean email addresses
$emails = array_map('trim', explode(',', $input));
$emails = array_filter($emails, fn($e) => !empty($e));
$emails = array_map('strtolower', $emails);
if (empty($emails)) {
echo "Ingen gyldige e-postadresser oppgitt.\n";
return;
}
echo "\n";
echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
$found = [];
// Search through all petition files
foreach (getPetitionFiles() as $petitionId) {
$result = modifyPetitionFile($petitionId, function(&$rows, $petitionId) use ($emails) {
$found = [];
$modified = false;
foreach ($rows as &$row) {
if (!isset($row[1]) || !isset($row[6])) {
continue;
}
$rowEmail = strtolower($row[1]);
if (in_array($rowEmail, $emails)) {
if ($row[6] === 'pending') {
$row[6] = 'confirmed';
$modified = true;
$found[] = [
'email' => $row[1],
'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
'petition_id' => $petitionId,
'was_pending' => true
];
} else {
$found[] = [
'email' => $row[1],
'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
'petition_id' => $petitionId,
'was_pending' => false,
'status' => $row[6]
];
}
}
}
unset($row);
return ['found' => $found, 'modified' => $modified];
});
if ($result && !empty($result['found'])) {
$found = array_merge($found, $result['found']);
}
}
// Determine which emails were not found
$foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
$notFound = [];
foreach ($emails as $email) {
if (!in_array($email, $foundEmails)) {
$notFound[] = $email;
}
}
// Display results
if (!empty($found)) {
printColor("Funnet:\n", 'green');
foreach ($found as $entry) {
$status = $entry['was_pending']
? "BEKREFTET"
: "allerede " . $entry['status'];
echo " - {$entry['email']} ({$entry['name']}) - {$entry['petition_id']}: ";
if ($entry['was_pending']) {
printColor("{$status}\n", 'green');
} else {
printColor("{$status}\n", 'yellow');
}
}
}
if (!empty($notFound)) {
echo "\n";
printColor("Ikke funnet:\n", 'red');
foreach ($notFound as $email) {
echo " - {$email}\n";
}
}
$confirmedCount = count(array_filter($found, fn($f) => $f['was_pending']));
echo "\n";
printColor("Ferdig: {$confirmedCount} signatur(er) bekreftet\n", 'green');
}
/**
* Manually delete signatures by email address
*/
function manuallyDeleteSignatures(): void {
echo "\n";
printColor("Slett signaturer\n", 'cyan');
echo "\n";
$input = prompt("Skriv inn e-postadresse(r) (kommaseparert): ");
if (empty(trim($input))) {
echo "Ingen e-postadresser oppgitt.\n";
return;
}
// Parse and clean email addresses
$emails = array_map('trim', explode(',', $input));
$emails = array_filter($emails, fn($e) => !empty($e));
$emails = array_map('strtolower', $emails);
if (empty($emails)) {
echo "Ingen gyldige e-postadresser oppgitt.\n";
return;
}
echo "\n";
echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
$found = [];
// First pass: find all matching signatures (read-only)
foreach (getPetitionFiles() as $petitionId) {
$csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
if (!file_exists($csvPath)) {
continue;
}
$fp = fopen($csvPath, 'r');
if (!$fp) {
continue;
}
if (!flock($fp, LOCK_SH)) {
fclose($fp);
continue;
}
fgetcsv($fp, null, ',', '"', ''); // Skip header
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
if (!isset($row[1])) {
continue;
}
$rowEmail = strtolower($row[1]);
if (in_array($rowEmail, $emails)) {
$found[] = [
'email' => $row[1],
'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
'petition_id' => $petitionId,
'status' => $row[6] ?? 'unknown',
'timestamp' => (int)($row[0] ?? 0)
];
}
}
flock($fp, LOCK_UN);
fclose($fp);
}
// Determine which emails were not found
$foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
$notFound = [];
foreach ($emails as $email) {
if (!in_array($email, $foundEmails)) {
$notFound[] = $email;
}
}
if (!empty($notFound)) {
printColor("Ikke funnet:\n", 'red');
foreach ($notFound as $email) {
echo " - {$email}\n";
}
echo "\n";
}
if (empty($found)) {
echo "Ingen signaturer a slette.\n";
return;
}
// Show what will be deleted
printColor("Folgende signaturer vil bli slettet:\n", 'yellow');
foreach ($found as $entry) {
$date = formatDate($entry['timestamp']);
echo " - {$entry['email']} ({$entry['name']}) - {$entry['petition_id']} [{$entry['status']}] {$date}\n";
}
echo "\n";
if (!confirm("Er du sikker pa at du vil slette " . count($found) . " signatur(er)?")) {
echo "Avbrutt.\n";
return;
}
// Second pass: delete the signatures
$deleted = 0;
$emailsToDelete = array_map(fn($f) => strtolower($f['email']), $found);
foreach (getPetitionFiles() as $petitionId) {
$result = modifyPetitionFile($petitionId, function(&$rows, $petitionId) use ($emailsToDelete) {
$deletedCount = 0;
$rows = array_filter($rows, function($row) use ($emailsToDelete, &$deletedCount) {
if (isset($row[1]) && in_array(strtolower($row[1]), $emailsToDelete)) {
$deletedCount++;
return false; // Remove this row
}
return true; // Keep this row
});
$rows = array_values($rows); // Re-index
return ['deleted' => $deletedCount];
});
if ($result && isset($result['deleted'])) {
$deleted += $result['deleted'];
}
}
echo "\n";
printColor("Ferdig: {$deleted} signatur(er) slettet\n", 'green');
}
/** /**
* Interactive ignore marking * Interactive ignore marking
*/ */
@ -823,13 +1132,19 @@ function main(): void {
markAsIgnored(); markAsIgnored();
break; break;
case '6': case '6':
manuallyConfirmSignatures();
break;
case '7':
manuallyDeleteSignatures();
break;
case '8':
case 'q': case 'q':
case 'quit': case 'quit':
case 'exit': case 'exit':
echo "Ha det!\n"; echo "Ha det!\n";
exit(0); exit(0);
default: default:
printColor("Ugyldig valg. Velg 1-6.\n", 'red'); printColor("Ugyldig valg. Velg 1-8.\n", 'red');
} }
} }
} }