Add ignore list functionality for petition management
Add functionality to mark entries as ignored for malformed emails and other issues Add ignore list file and management functions Update menu to include ignore option Implement interactive ignore marking for failed emails and unconfirmed signatures Add color-coded output for better visibility Update function signatures to support ignore list filtering
This commit is contained in:
parent
7659b0cf5e
commit
5194ba8213
1 changed files with 257 additions and 8 deletions
|
|
@ -7,6 +7,7 @@
|
|||
* - View failed email sends from SMTP log
|
||||
* - View unconfirmed signatures from petition CSVs
|
||||
* - Retry sending emails with rate limiting (250/hour max)
|
||||
* - Mark entries as ignored (for malformed emails etc.)
|
||||
*
|
||||
* Usage: php custom/petition-cli.php
|
||||
*/
|
||||
|
|
@ -20,6 +21,7 @@ define('BASE_DIR', __DIR__);
|
|||
define('DATA_DIR', BASE_DIR . '/data');
|
||||
define('PETITIONS_DIR', DATA_DIR . '/petitions');
|
||||
define('SMTP_LOG', DATA_DIR . '/smtp-log.csv');
|
||||
define('IGNORE_LIST', DATA_DIR . '/petition-ignore.csv');
|
||||
|
||||
// Rate limit: 250 emails/hour = 1 email per 14.4 seconds, using 15 seconds
|
||||
define('EMAIL_DELAY_SECONDS', 15);
|
||||
|
|
@ -49,14 +51,93 @@ function getPetitionFiles(): array {
|
|||
}, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load ignore list
|
||||
*/
|
||||
function loadIgnoreList(): array {
|
||||
if (!file_exists(IGNORE_LIST)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ignored = [];
|
||||
$fp = fopen(IGNORE_LIST, 'r');
|
||||
if (!$fp) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Skip header
|
||||
fgetcsv($fp, null, ',', '"', '');
|
||||
|
||||
// CSV format: timestamp, type, petition_id, email, reason
|
||||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
|
||||
if (isset($row[3])) {
|
||||
// Key format: type|petition_id|email
|
||||
$key = ($row[1] ?? '') . '|' . ($row[2] ?? '') . '|' . strtolower($row[3]);
|
||||
$ignored[$key] = [
|
||||
'timestamp' => (int)$row[0],
|
||||
'type' => $row[1],
|
||||
'petition_id' => $row[2],
|
||||
'email' => $row[3],
|
||||
'reason' => $row[4] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
return $ignored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entry to ignore list
|
||||
*/
|
||||
function addToIgnoreList(string $type, string $petitionId, string $email, string $reason): bool {
|
||||
$isNewFile = !file_exists(IGNORE_LIST);
|
||||
|
||||
$fp = fopen(IGNORE_LIST, 'a');
|
||||
if (!$fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flock($fp, LOCK_EX)) {
|
||||
if ($isNewFile) {
|
||||
fputcsv($fp, ['timestamp', 'type', 'petition_id', 'email', 'reason'], ',', '"', '');
|
||||
}
|
||||
|
||||
fputcsv($fp, [
|
||||
time(),
|
||||
$type,
|
||||
$petitionId,
|
||||
$email,
|
||||
$reason
|
||||
], ',', '"', '');
|
||||
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entry is ignored
|
||||
*/
|
||||
function isIgnored(array $ignoreList, string $type, string $petitionId, string $email): bool {
|
||||
$key = $type . '|' . $petitionId . '|' . strtolower($email);
|
||||
return isset($ignoreList[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read failed emails from SMTP log
|
||||
*/
|
||||
function getFailedEmails(): array {
|
||||
function getFailedEmails(bool $excludeIgnored = true): array {
|
||||
if (!file_exists(SMTP_LOG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ignoreList = $excludeIgnored ? loadIgnoreList() : [];
|
||||
|
||||
$failed = [];
|
||||
$fp = fopen(SMTP_LOG, 'r');
|
||||
if (!$fp) {
|
||||
|
|
@ -69,13 +150,20 @@ function getFailedEmails(): array {
|
|||
// CSV format: timestamp, type, petition_id, email, status, error_message
|
||||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
|
||||
if (isset($row[4]) && $row[4] === 'failed') {
|
||||
$failed[] = [
|
||||
$entry = [
|
||||
'timestamp' => (int)$row[0],
|
||||
'type' => $row[1],
|
||||
'petition_id' => $row[2],
|
||||
'email' => $row[3],
|
||||
'error' => $row[5] ?? ''
|
||||
];
|
||||
|
||||
// Skip if ignored
|
||||
if ($excludeIgnored && isIgnored($ignoreList, $entry['type'], $entry['petition_id'], $entry['email'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$failed[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,12 +189,14 @@ function getFailedEmails(): array {
|
|||
/**
|
||||
* Get unconfirmed signatures from a petition CSV
|
||||
*/
|
||||
function getUnconfirmedSignatures(string $petitionId): array {
|
||||
function getUnconfirmedSignatures(string $petitionId, bool $excludeIgnored = true): array {
|
||||
$csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
|
||||
if (!file_exists($csvPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ignoreList = $excludeIgnored ? loadIgnoreList() : [];
|
||||
|
||||
$unconfirmed = [];
|
||||
$fp = fopen($csvPath, 'r');
|
||||
if (!$fp) {
|
||||
|
|
@ -120,7 +210,7 @@ function getUnconfirmedSignatures(string $petitionId): array {
|
|||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
|
||||
if (isset($row[6]) && $row[6] === 'pending') {
|
||||
$tokenCreated = isset($row[8]) ? (int)$row[8] : (int)$row[0];
|
||||
$unconfirmed[] = [
|
||||
$entry = [
|
||||
'timestamp' => (int)$row[0],
|
||||
'email' => $row[1],
|
||||
'firstname' => $row[2],
|
||||
|
|
@ -131,6 +221,13 @@ function getUnconfirmedSignatures(string $petitionId): array {
|
|||
'token_created' => $tokenCreated,
|
||||
'petition_id' => $petitionId
|
||||
];
|
||||
|
||||
// Skip if ignored
|
||||
if ($excludeIgnored && isIgnored($ignoreList, 'unconfirmed', $petitionId, $entry['email'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unconfirmed[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,10 +242,10 @@ function getUnconfirmedSignatures(string $petitionId): array {
|
|||
/**
|
||||
* Get all unconfirmed signatures from all petitions
|
||||
*/
|
||||
function getAllUnconfirmedSignatures(): array {
|
||||
function getAllUnconfirmedSignatures(bool $excludeIgnored = true): array {
|
||||
$all = [];
|
||||
foreach (getPetitionFiles() as $petitionId) {
|
||||
$all = array_merge($all, getUnconfirmedSignatures($petitionId));
|
||||
$all = array_merge($all, getUnconfirmedSignatures($petitionId, $excludeIgnored));
|
||||
}
|
||||
|
||||
// Sort by timestamp descending
|
||||
|
|
@ -268,6 +365,7 @@ function printColor(string $text, string $color): void {
|
|||
'green' => "\033[32m",
|
||||
'yellow' => "\033[33m",
|
||||
'blue' => "\033[34m",
|
||||
'cyan' => "\033[36m",
|
||||
'reset' => "\033[0m"
|
||||
];
|
||||
|
||||
|
|
@ -302,7 +400,8 @@ function showMenu(): void {
|
|||
echo " 2) Vis ubekreftede signaturer\n";
|
||||
echo " 3) Send e-post pa nytt til mislykkede\n";
|
||||
echo " 4) Send bekreftelse pa nytt til ubekreftede\n";
|
||||
echo " 5) Avslutt\n";
|
||||
echo " 5) Marker oppforinger som ignorert\n";
|
||||
echo " 6) Avslutt\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
|
|
@ -537,6 +636,153 @@ function resendToUnconfirmed(): void {
|
|||
printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive ignore marking
|
||||
*/
|
||||
function markAsIgnored(): void {
|
||||
echo "\n";
|
||||
printColor("Marker oppforinger som ignorert\n", 'cyan');
|
||||
echo "\n";
|
||||
echo "Velg liste:\n";
|
||||
echo " 1) Mislykkede e-poster fra SMTP-logg\n";
|
||||
echo " 2) Ubekreftede signaturer\n";
|
||||
echo " 3) Tilbake\n";
|
||||
echo "\n";
|
||||
|
||||
$choice = prompt('Valg: ');
|
||||
|
||||
if ($choice === '1') {
|
||||
markFailedAsIgnored();
|
||||
} elseif ($choice === '2') {
|
||||
markUnconfirmedAsIgnored();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark failed emails as ignored (one by one)
|
||||
*/
|
||||
function markFailedAsIgnored(): void {
|
||||
$failed = getFailedEmails();
|
||||
|
||||
if (empty($failed)) {
|
||||
printColor("\nIngen mislykkede e-poster a markere.\n", 'green');
|
||||
return;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
printColor("Ga gjennom mislykkede e-poster en etter en.\n", 'cyan');
|
||||
echo "Trykk 'j' for a ignorere, 'n' for a beholde, 's' for a hoppe over resten.\n\n";
|
||||
|
||||
$ignored = 0;
|
||||
$kept = 0;
|
||||
|
||||
foreach ($failed as $i => $entry) {
|
||||
$num = $i + 1;
|
||||
$total = count($failed);
|
||||
$date = formatDate($entry['timestamp']);
|
||||
$type = $entry['type'] === 'confirmation' ? 'Bekreftelse' : 'Takk';
|
||||
|
||||
echo str_repeat('-', 60) . "\n";
|
||||
echo "[{$num}/{$total}]\n";
|
||||
echo " E-post: {$entry['email']}\n";
|
||||
echo " Type: {$type}\n";
|
||||
echo " Kampanje: {$entry['petition_id']}\n";
|
||||
echo " Dato: {$date}\n";
|
||||
echo " Feil: " . ($entry['error'] ?: '(ukjent)') . "\n";
|
||||
|
||||
$response = strtolower(prompt("\nIgnorer denne? [j/n/s]: "));
|
||||
|
||||
if ($response === 's' || $response === 'stopp' || $response === 'stop') {
|
||||
echo "Avslutter gjennomgang.\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes') {
|
||||
$reason = prompt("Grunn (valgfritt): ");
|
||||
if (addToIgnoreList($entry['type'], $entry['petition_id'], $entry['email'], $reason)) {
|
||||
printColor("Markert som ignorert.\n", 'green');
|
||||
$ignored++;
|
||||
} else {
|
||||
printColor("Kunne ikke lagre til ignorliste.\n", 'red');
|
||||
}
|
||||
} else {
|
||||
echo "Beholdt.\n";
|
||||
$kept++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
printColor("Ferdig: {$ignored} ignorert, {$kept} beholdt\n", 'green');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark unconfirmed signatures as ignored (one by one)
|
||||
*/
|
||||
function markUnconfirmedAsIgnored(): void {
|
||||
$unconfirmed = getAllUnconfirmedSignatures();
|
||||
|
||||
// Include expired for review
|
||||
$currentTime = time();
|
||||
|
||||
if (empty($unconfirmed)) {
|
||||
printColor("\nIngen ubekreftede signaturer a markere.\n", 'green');
|
||||
return;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
printColor("Ga gjennom ubekreftede signaturer en etter en.\n", 'cyan');
|
||||
echo "Trykk 'j' for a ignorere, 'n' for a beholde, 's' for a hoppe over resten.\n\n";
|
||||
|
||||
$ignored = 0;
|
||||
$kept = 0;
|
||||
|
||||
foreach ($unconfirmed as $i => $sig) {
|
||||
$num = $i + 1;
|
||||
$total = count($unconfirmed);
|
||||
$date = formatDate($sig['timestamp']);
|
||||
$name = $sig['firstname'] . ' ' . $sig['surname'];
|
||||
$daysLeft = 30 - floor(($currentTime - $sig['token_created']) / 86400);
|
||||
$expired = $daysLeft < 0;
|
||||
|
||||
echo str_repeat('-', 60) . "\n";
|
||||
echo "[{$num}/{$total}]";
|
||||
if ($expired) {
|
||||
printColor(" (UTLOPT)", 'red');
|
||||
}
|
||||
echo "\n";
|
||||
echo " E-post: {$sig['email']}\n";
|
||||
echo " Navn: {$name}\n";
|
||||
echo " Kampanje: {$sig['petition_id']}\n";
|
||||
echo " Dato: {$date}\n";
|
||||
if (!$expired) {
|
||||
echo " Utloper: om {$daysLeft} dager\n";
|
||||
}
|
||||
|
||||
$response = strtolower(prompt("\nIgnorer denne? [j/n/s]: "));
|
||||
|
||||
if ($response === 's' || $response === 'stopp' || $response === 'stop') {
|
||||
echo "Avslutter gjennomgang.\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes') {
|
||||
$reason = prompt("Grunn (valgfritt): ");
|
||||
if (addToIgnoreList('unconfirmed', $sig['petition_id'], $sig['email'], $reason)) {
|
||||
printColor("Markert som ignorert.\n", 'green');
|
||||
$ignored++;
|
||||
} else {
|
||||
printColor("Kunne ikke lagre til ignorliste.\n", 'red');
|
||||
}
|
||||
} else {
|
||||
echo "Beholdt.\n";
|
||||
$kept++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
printColor("Ferdig: {$ignored} ignorert, {$kept} beholdt\n", 'green');
|
||||
}
|
||||
|
||||
// Main loop
|
||||
function main(): void {
|
||||
// Check requirements
|
||||
|
|
@ -568,13 +814,16 @@ function main(): void {
|
|||
resendToUnconfirmed();
|
||||
break;
|
||||
case '5':
|
||||
markAsIgnored();
|
||||
break;
|
||||
case '6':
|
||||
case 'q':
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
echo "Ha det!\n";
|
||||
exit(0);
|
||||
default:
|
||||
printColor("Ugyldig valg. Velg 1-5.\n", 'red');
|
||||
printColor("Ugyldig valg. Velg 1-6.\n", 'red');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue