innhold/custom/petition-cli.php
Ruben fdbf9a3210 Add manual signature confirmation by email address
Add new menu option to manually confirm signatures by email address
Search through all petition files to find matching emails
Update status from pending to confirmed when found
Display results with confirmation status and petition IDs
Handle multiple emails via comma-separated input
Maintain proper file locking to prevent race conditions
2026-02-01 20:27:34 +01:00

988 lines
29 KiB
PHP
Executable file

#!/usr/bin/env php
<?php
/**
* Petition Email CLI Tool
*
* Interactive tool for managing petition emails:
* - 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
*/
// Ensure CLI only
if (php_sapi_name() !== 'cli') {
die("This script can only be run from the command line.\n");
}
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);
/**
* Load SMTP configuration
*/
function loadSmtpConfig(): ?array {
$configPath = BASE_DIR . '/smtp-config.php';
if (!file_exists($configPath)) {
return null;
}
return require $configPath;
}
/**
* Get all petition CSV files
*/
function getPetitionFiles(): array {
if (!is_dir(PETITIONS_DIR)) {
return [];
}
$files = glob(PETITIONS_DIR . '/*.csv');
return array_map(function($f) {
return basename($f, '.csv');
}, $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(bool $excludeIgnored = true): array {
if (!file_exists(SMTP_LOG)) {
return [];
}
$ignoreList = $excludeIgnored ? loadIgnoreList() : [];
$failed = [];
$fp = fopen(SMTP_LOG, 'r');
if (!$fp) {
return [];
}
// Skip header
fgetcsv($fp, null, ',', '"', '');
// CSV format: timestamp, type, petition_id, email, status, error_message
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
if (isset($row[4]) && $row[4] === '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;
}
}
fclose($fp);
// Sort by timestamp descending (newest first)
usort($failed, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
// Remove duplicates (keep only latest failure per email+type+petition)
$unique = [];
$seen = [];
foreach ($failed as $entry) {
$key = $entry['email'] . '|' . $entry['type'] . '|' . $entry['petition_id'];
if (!isset($seen[$key])) {
$seen[$key] = true;
$unique[] = $entry;
}
}
return $unique;
}
/**
* Get unconfirmed signatures from a petition CSV
*/
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) {
return [];
}
// Skip header
fgetcsv($fp, null, ',', '"', '');
// CSV format: timestamp, email, firstname, surname, region, display, status, token, token_created, ip_hash
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
if (isset($row[6]) && $row[6] === 'pending') {
$tokenCreated = isset($row[8]) ? (int)$row[8] : (int)$row[0];
$entry = [
'timestamp' => (int)$row[0],
'email' => $row[1],
'firstname' => $row[2],
'surname' => $row[3],
'region' => $row[4],
'display' => $row[5],
'token' => $row[7],
'token_created' => $tokenCreated,
'petition_id' => $petitionId
];
// Skip if ignored
if ($excludeIgnored && isIgnored($ignoreList, 'unconfirmed', $petitionId, $entry['email'])) {
continue;
}
$unconfirmed[] = $entry;
}
}
fclose($fp);
// Sort by timestamp descending
usort($unconfirmed, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
return $unconfirmed;
}
/**
* Get all unconfirmed signatures from all petitions
*/
function getAllUnconfirmedSignatures(bool $excludeIgnored = true): array {
$all = [];
foreach (getPetitionFiles() as $petitionId) {
$all = array_merge($all, getUnconfirmedSignatures($petitionId, $excludeIgnored));
}
// Sort by timestamp descending
usort($all, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
return $all;
}
/**
* Send a single email using PHPMailer
*/
function sendEmail(string $to, string $toName, string $subject, string $body): array {
$config = loadSmtpConfig();
if (!$config || !$config['enabled']) {
return ['success' => false, 'error' => 'SMTP ikke konfigurert eller deaktivert'];
}
$fromEmail = $config['petition']['from_email'] ?? $config['from_email'];
$fromName = $config['petition']['from_name'] ?? $config['from_name'];
// Get petition-specific SMTP settings (allows separate SMTP account for better deliverability)
$smtpHost = $config['petition']['host'] ?? $config['host'];
$smtpPort = $config['petition']['port'] ?? $config['port'];
$smtpUser = $config['petition']['username'] ?? $config['username'];
$smtpPass = $config['petition']['password'] ?? $config['password'];
// Pre-flight check
$fp = @fsockopen($smtpHost, $smtpPort, $errno, $errstr, 10);
if (!$fp) {
return ['success' => false, 'error' => "Tilkobling feilet: {$errno} - {$errstr}"];
}
fclose($fp);
try {
require_once BASE_DIR . '/vendor/PHPMailer.Lite.php';
$mail = new \codeworxtech\PHPMailerLite\PHPMailerLite();
$mail->SetSMTPhost($smtpHost);
$mail->SetSMTPport($smtpPort);
$mail->SetSMTPuser($smtpUser);
$mail->SetSMTPpass($smtpPass);
$mail->SetSender([$fromEmail => $fromName]);
$mail->AddRecipient([$to => $toName]);
$mail->SetSubject($subject);
$mail->SetBodyText($body);
ob_start();
$sent = @$mail->Send('smtp');
$output = ob_get_clean();
if (!$sent || stripos($output, 'error') !== false || stripos($output, '&#10007;') !== false) {
return ['success' => false, 'error' => strip_tags($output) ?: 'Sending feilet'];
}
return ['success' => true, 'error' => ''];
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Build confirmation email body
*/
function buildConfirmationEmail(array $signature, string $petitionTitle): array {
$confirmUrl = "https://stopplidelsen.no/underskriftskampanjer/{$signature['petition_id']}/?confirm={$signature['token']}#sign-now";
$body = "Hei {$signature['firstname']}!\n\n";
$body .= "Takk for at du signerte underskriftskampanjen \"{$petitionTitle}\".\n\n";
$body .= "Klikk lenken under for a bekrefte signaturen din:\n";
$body .= "{$confirmUrl}\n\n";
$body .= "Lenken utloper om 30 dager.\n\n";
$body .= "Hvis du ikke signerte denne kampanjen, kan du trygt ignorere denne e-posten.\n\n";
$body .= "---\n\n";
$body .= "Du kan trekke tilbake signaturen din nar som helst ved a folge lenken i bekreftelsese-posten.\n\n";
$body .= "Med vennlig hilsen,\n";
$body .= "Stopp lidelsen\n";
return [
'subject' => 'Bekreft signaturen din',
'body' => $body
];
}
/**
* Log email result to SMTP log
*/
function logEmailResult(string $type, string $petitionId, string $email, bool $success, string $error = ''): void {
$fp = fopen(SMTP_LOG, 'a');
if (!$fp) {
return;
}
if (flock($fp, LOCK_EX)) {
fputcsv($fp, [
time(),
$type,
$petitionId,
$email,
$success ? 'success' : 'failed',
$error
], ',', '"', '');
flock($fp, LOCK_UN);
}
fclose($fp);
}
/**
* Format timestamp for display
*/
function formatDate(int $timestamp): string {
return date('Y-m-d H:i', $timestamp);
}
/**
* Print colored output
*/
function printColor(string $text, string $color): void {
$colors = [
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'blue' => "\033[34m",
'cyan' => "\033[36m",
'reset' => "\033[0m"
];
echo ($colors[$color] ?? '') . $text . $colors['reset'];
}
/**
* Read user input
*/
function prompt(string $message): string {
echo $message;
return trim(fgets(STDIN));
}
/**
* Confirm action
*/
function confirm(string $message): bool {
$response = strtolower(prompt($message . ' [j/n]: '));
return $response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes';
}
/**
* Display main menu
*/
function showMenu(): void {
echo "\n";
printColor("=== Underskriftskampanje E-post CLI ===\n", 'blue');
echo "\n";
echo "Velg handling:\n";
echo " 1) Vis mislykkede e-poster fra SMTP-logg\n";
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) Marker oppforinger som ignorert\n";
echo " 6) Manuell bekreftelse av signaturer\n";
echo " 7) Avslutt\n";
echo "\n";
}
/**
* List failed emails
*/
function listFailedEmails(): void {
$failed = getFailedEmails();
if (empty($failed)) {
printColor("\nIngen mislykkede e-poster i loggen.\n", 'green');
return;
}
echo "\n";
printColor("Mislykkede e-poster (" . count($failed) . " stk):\n", 'yellow');
echo str_repeat('-', 80) . "\n";
foreach ($failed as $i => $entry) {
$num = $i + 1;
$date = formatDate($entry['timestamp']);
$type = $entry['type'] === 'confirmation' ? 'Bekreftelse' : 'Takk';
echo sprintf(
"%3d. %-35s %-12s %-20s\n Feil: %s\n",
$num,
$entry['email'],
$type,
$date,
$entry['error'] ?: '(ukjent)'
);
}
echo str_repeat('-', 80) . "\n";
}
/**
* List unconfirmed signatures
*/
function listUnconfirmedSignatures(): void {
$unconfirmed = getAllUnconfirmedSignatures();
if (empty($unconfirmed)) {
printColor("\nIngen ubekreftede signaturer.\n", 'green');
return;
}
// Filter out expired tokens (older than 30 days)
$currentTime = time();
$valid = array_filter($unconfirmed, function($sig) use ($currentTime) {
return ($currentTime - $sig['token_created']) <= 2592000; // 30 days
});
$expired = count($unconfirmed) - count($valid);
echo "\n";
printColor("Ubekreftede signaturer (" . count($valid) . " gyldige", 'yellow');
if ($expired > 0) {
echo ", {$expired} utlopte";
}
echo "):\n";
echo str_repeat('-', 80) . "\n";
foreach ($valid as $i => $sig) {
$num = $i + 1;
$date = formatDate($sig['timestamp']);
$name = $sig['firstname'] . ' ' . $sig['surname'];
$daysLeft = 30 - floor(($currentTime - $sig['token_created']) / 86400);
echo sprintf(
"%3d. %-35s %-20s\n %s (%d dager igjen)\n",
$num,
$sig['email'],
$name,
$date,
$daysLeft
);
}
echo str_repeat('-', 80) . "\n";
}
/**
* Retry sending failed emails
*/
function retryFailedEmails(): void {
$failed = getFailedEmails();
if (empty($failed)) {
printColor("\nIngen mislykkede e-poster a sende pa nytt.\n", 'green');
return;
}
listFailedEmails();
if (!confirm("\nSende pa nytt til alle " . count($failed) . " adresser?")) {
echo "Avbrutt.\n";
return;
}
$total = count($failed);
$estimatedMinutes = ceil(($total * EMAIL_DELAY_SECONDS) / 60);
echo "\n";
printColor("Sender med " . EMAIL_DELAY_SECONDS . " sekunders mellomrom (maks 250/time)...\n", 'blue');
echo "Estimert tid: ca. {$estimatedMinutes} minutter\n\n";
$success = 0;
$failures = 0;
foreach ($failed as $i => $entry) {
$num = $i + 1;
echo "[{$num}/{$total}] {$entry['email']} ... ";
// For now, we can only retry confirmation emails properly
// Thank you emails need more context that we don't have
if ($entry['type'] !== 'confirmation') {
printColor("HOPPET OVER (type: {$entry['type']})\n", 'yellow');
continue;
}
// We need to get the signature data from the petition CSV
$signatures = getUnconfirmedSignatures($entry['petition_id']);
$signature = null;
foreach ($signatures as $sig) {
if (strtolower($sig['email']) === strtolower($entry['email'])) {
$signature = $sig;
break;
}
}
if (!$signature) {
printColor("HOPPET OVER (signatur ikke funnet eller allerede bekreftet)\n", 'yellow');
continue;
}
// Check token expiry
if ((time() - $signature['token_created']) > 2592000) {
printColor("HOPPET OVER (token utlopt)\n", 'yellow');
continue;
}
$email = buildConfirmationEmail($signature, $entry['petition_id']);
$result = sendEmail(
$signature['email'],
$signature['firstname'] . ' ' . $signature['surname'],
$email['subject'],
$email['body']
);
if ($result['success']) {
printColor("OK\n", 'green');
logEmailResult('confirmation', $entry['petition_id'], $signature['email'], true);
$success++;
} else {
printColor("FEILET: {$result['error']}\n", 'red');
logEmailResult('confirmation', $entry['petition_id'], $signature['email'], false, $result['error']);
$failures++;
}
// Wait before next email (except for last one)
if ($i < $total - 1) {
sleep(EMAIL_DELAY_SECONDS);
}
}
echo "\n";
printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
}
/**
* Resend confirmation to unconfirmed signatures
*/
function resendToUnconfirmed(): void {
$unconfirmed = getAllUnconfirmedSignatures();
// Filter out expired tokens
$currentTime = time();
$valid = array_filter($unconfirmed, function($sig) use ($currentTime) {
return ($currentTime - $sig['token_created']) <= 2592000;
});
$valid = array_values($valid); // Re-index
if (empty($valid)) {
printColor("\nIngen gyldige ubekreftede signaturer a sende til.\n", 'green');
return;
}
listUnconfirmedSignatures();
if (!confirm("\nSende bekreftelse pa nytt til alle " . count($valid) . " adresser?")) {
echo "Avbrutt.\n";
return;
}
$total = count($valid);
$estimatedMinutes = ceil(($total * EMAIL_DELAY_SECONDS) / 60);
echo "\n";
printColor("Sender med " . EMAIL_DELAY_SECONDS . " sekunders mellomrom (maks 250/time)...\n", 'blue');
echo "Estimert tid: ca. {$estimatedMinutes} minutter\n\n";
$success = 0;
$failures = 0;
foreach ($valid as $i => $signature) {
$num = $i + 1;
echo "[{$num}/{$total}] {$signature['email']} ... ";
$email = buildConfirmationEmail($signature, $signature['petition_id']);
$result = sendEmail(
$signature['email'],
$signature['firstname'] . ' ' . $signature['surname'],
$email['subject'],
$email['body']
);
if ($result['success']) {
printColor("OK\n", 'green');
logEmailResult('confirmation', $signature['petition_id'], $signature['email'], true);
$success++;
} else {
printColor("FEILET: {$result['error']}\n", 'red');
logEmailResult('confirmation', $signature['petition_id'], $signature['email'], false, $result['error']);
$failures++;
}
// Wait before next email (except for last one)
if ($i < $total - 1) {
sleep(EMAIL_DELAY_SECONDS);
}
}
echo "\n";
printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
}
/**
* 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 = [];
$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;
}
$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);
// 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, ',', '"', '');
}
}
flock($fp, LOCK_UN);
fclose($fp);
}
// Determine which emails were not found
$foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
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');
}
/**
* 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
$config = loadSmtpConfig();
if (!$config) {
printColor("Feil: SMTP-konfigurasjon ikke funnet (smtp-config.php)\n", 'red');
exit(1);
}
if (!$config['enabled']) {
printColor("Advarsel: SMTP er deaktivert i konfigurasjonen\n", 'yellow');
}
while (true) {
showMenu();
$choice = prompt('Valg: ');
switch ($choice) {
case '1':
listFailedEmails();
break;
case '2':
listUnconfirmedSignatures();
break;
case '3':
retryFailedEmails();
break;
case '4':
resendToUnconfirmed();
break;
case '5':
markAsIgnored();
break;
case '6':
manuallyConfirmSignatures();
break;
case '7':
case 'q':
case 'quit':
case 'exit':
echo "Ha det!\n";
exit(0);
default:
printColor("Ugyldig valg. Velg 1-7.\n", 'red');
}
}
}
// Run
main();