Add signature deletion functionality to petition CLI
Introduce new modifyPetitionFile helper function for atomic CSV operations Refactor manual confirmation to use the new helper function Add new manuallyDeleteSignatures function with confirmation flow Update menu to include deletion option and adjust numbering
This commit is contained in:
parent
fdbf9a3210
commit
fffd51422c
1 changed files with 238 additions and 73 deletions
|
|
@ -408,7 +408,8 @@ function showMenu(): void {
|
|||
echo " 4) Send bekreftelse pa nytt til ubekreftede\n";
|
||||
echo " 5) Marker oppforinger som ignorert\n";
|
||||
echo " 6) Manuell bekreftelse av signaturer\n";
|
||||
echo " 7) Avslutt\n";
|
||||
echo " 7) Slett signaturer\n";
|
||||
echo " 8) Avslutt\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
|
|
@ -643,6 +644,73 @@ function resendToUnconfirmed(): void {
|
|||
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
|
||||
*/
|
||||
|
|
@ -671,89 +739,53 @@ function manuallyConfirmSignatures(): void {
|
|||
echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
|
||||
|
||||
$found = [];
|
||||
$notFound = [];
|
||||
|
||||
// Search through all petition files
|
||||
foreach (getPetitionFiles() as $petitionId) {
|
||||
$csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
|
||||
if (!file_exists($csvPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flock($fp, LOCK_EX)) {
|
||||
fclose($fp);
|
||||
printColor(" Advarsel: Kunne ikke lase {$petitionId}.csv\n", 'yellow');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read all rows while holding the lock
|
||||
$rows = [];
|
||||
$header = fgetcsv($fp, null, ',', '"', '');
|
||||
if ($header === false) {
|
||||
// Empty file, skip
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$modified = false;
|
||||
foreach ($rows as &$row) {
|
||||
if (!isset($row[1]) || !isset($row[6])) {
|
||||
continue;
|
||||
}
|
||||
$result = modifyPetitionFile($petitionId, function(&$rows, $petitionId) use ($emails) {
|
||||
$found = [];
|
||||
$modified = false;
|
||||
|
||||
$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]
|
||||
];
|
||||
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);
|
||||
unset($row);
|
||||
|
||||
return ['found' => $found, 'modified' => $modified];
|
||||
});
|
||||
|
||||
// Write back if modified (still holding the lock)
|
||||
if ($modified) {
|
||||
rewind($fp);
|
||||
ftruncate($fp, 0);
|
||||
fputcsv($fp, $header, ',', '"', '');
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($fp, $row, ',', '"', '');
|
||||
}
|
||||
if ($result && !empty($result['found'])) {
|
||||
$found = array_merge($found, $result['found']);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -789,6 +821,136 @@ function manuallyConfirmSignatures(): void {
|
|||
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
|
||||
*/
|
||||
|
|
@ -973,13 +1135,16 @@ function main(): void {
|
|||
manuallyConfirmSignatures();
|
||||
break;
|
||||
case '7':
|
||||
manuallyDeleteSignatures();
|
||||
break;
|
||||
case '8':
|
||||
case 'q':
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
echo "Ha det!\n";
|
||||
exit(0);
|
||||
default:
|
||||
printColor("Ugyldig valg. Velg 1-7.\n", 'red');
|
||||
printColor("Ugyldig valg. Velg 1-8.\n", 'red');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue