Add resend confirmation functionality for petition signatures

Add link to resend confirmation page from main form
Add new resend confirmation page with form
Implement backend logic to handle resend requests
Add translations for new functionality
Update thank you page with resend confirmation link
This commit is contained in:
Ruben 2026-02-02 00:14:34 +01:00
parent fffd51422c
commit c8efa479bc
7 changed files with 208 additions and 1 deletions

View file

@ -799,6 +799,105 @@ function petitionGetSignatureByToken(string $csvPath, string $token): ?array {
return $signature;
}
/**
* Get pending signature data by email address
* Returns signature data if found and pending, null otherwise
*/
function petitionGetPendingSignatureByEmail(string $csvPath, string $email): ?array {
if (!file_exists($csvPath)) {
return null;
}
$fp = fopen($csvPath, 'r');
if (!$fp) {
return null;
}
$signature = null;
$email = strtolower(trim($email));
if (flock($fp, LOCK_SH)) {
fgetcsv($fp, null, ',', '"', ''); // Skip header
// CSV format: timestamp, email, firstname, surname, region, display, status, token, token_created, ip_hash
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
if (isset($row[1]) && strtolower($row[1]) === $email) {
$signature = [
'email' => $row[1],
'firstname' => $row[2],
'surname' => $row[3],
'region' => $row[4],
'display' => $row[5],
'status' => $row[6],
'token' => $row[7],
'token_created' => $row[8] ?? 0
];
break;
}
}
flock($fp, LOCK_UN);
}
fclose($fp);
return $signature;
}
/**
* Update token for an existing signature (for resend functionality)
*/
function petitionUpdateSignatureToken(string $csvPath, string $email, string $newToken): bool {
if (!file_exists($csvPath)) {
return false;
}
$fp = fopen($csvPath, 'r+');
if (!$fp) {
return false;
}
if (!flock($fp, LOCK_EX)) {
fclose($fp);
return false;
}
$rows = [];
$header = fgetcsv($fp, null, ',', '"', '');
$rows[] = $header;
$found = false;
$email = strtolower(trim($email));
$currentTime = time();
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
if (isset($row[1]) && strtolower($row[1]) === $email && $row[6] === 'pending') {
$row[7] = $newToken; // Update token
$row[8] = $currentTime; // Update token_created
$found = true;
}
$rows[] = $row;
}
if (!$found) {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
// Rewrite file
rewind($fp);
ftruncate($fp, 0);
foreach ($rows as $row) {
fputcsv($fp, $row, ',', '"', '');
}
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
/**
* Send thank you email with delete link (with retry wrapper)
*/
@ -1136,7 +1235,8 @@ function petitionGetPageData(?Context $ctx): ?array {
$csvPath = petitionGetCsvPath($petitionId);
// loadMetadata() already merges language-specific metadata via Hook::PROCESS_CONTENT
$petitionTitle = $metadata['title'] ?? $petitionId;
// Use petition_title if set (for subpages), otherwise fall back to page title
$petitionTitle = $metadata['petition_title'] ?? $metadata['title'] ?? $petitionId;
$thankYouPage = $metadata['thank_you_page'] ?? 'takk';
$formErrors = [];
@ -1195,6 +1295,64 @@ function petitionGetPageData(?Context $ctx): ?array {
}
}
// Handle resend confirmation request (POST from thank-you page)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['petition_resend'])) {
$resendEmail = strtolower(trim($_POST['resend_email'] ?? ''));
// Rate limit check (reuse existing IP rate limiting)
if (!petitionCheckIPRateLimit($petitionId . '-resend', 3, 300)) {
$confirmMessage = ['type' => 'error', 'text' => petitionT($ctx, 'petition', 'resend_rate_limit')];
} elseif (empty($resendEmail) || !filter_var($resendEmail, FILTER_VALIDATE_EMAIL)) {
$confirmMessage = ['type' => 'error', 'text' => petitionT($ctx, 'petition', 'email_required')];
} else {
// Look up signature by email
$signature = petitionGetPendingSignatureByEmail($csvPath, $resendEmail);
if ($signature === null) {
// Email not found at all
$confirmMessage = ['type' => 'error', 'text' => petitionT($ctx, 'petition', 'resend_not_found')];
} elseif ($signature['status'] === 'confirmed') {
// Already confirmed
$confirmMessage = ['type' => 'info', 'text' => petitionT($ctx, 'petition', 'resend_already_confirmed')];
} else {
// Generate new token and update signature
$newToken = bin2hex(random_bytes(32));
if (petitionUpdateSignatureToken($csvPath, $resendEmail, $newToken)) {
// Build confirmation URL
$langPrefix = $ctx->get('langPrefix', '');
$currentPath = trim($ctx->requestPath, '/');
// Remove subpage suffixes to get petition base path
$currentPath = preg_replace('#/(takk|send-bekreftelse-pa-nytt)$#', '', $currentPath);
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$confirmUrl = "{$protocol}://{$host}{$langPrefix}/{$currentPath}/?confirm={$newToken}#sign-now";
// Send confirmation email
$signatureData = [
'email' => $signature['email'],
'firstname' => $signature['firstname'],
'surname' => $signature['surname']
];
if (petitionSendConfirmationEmail($signatureData, $confirmUrl, $petitionTitle, $petitionId, $ctx)) {
$confirmMessage = ['type' => 'success', 'text' => petitionT($ctx, 'petition', 'resend_success')];
} else {
$confirmMessage = ['type' => 'error', 'text' => petitionT($ctx, 'petition', 'error_email_send')];
}
} else {
$confirmMessage = ['type' => 'error', 'text' => petitionT($ctx, 'petition', 'resend_not_found')];
}
}
}
// Store message in session and redirect back (PRG pattern)
$_SESSION['petition_resend_message'] = $confirmMessage;
$langPrefix = $ctx->get('langPrefix', '');
$currentPath = trim($ctx->requestPath, '/');
header("Location: {$langPrefix}/{$currentPath}/");
exit;
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['petition_submit'])) {
@ -1401,5 +1559,11 @@ Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
unset($_SESSION['petition_subscribed_newsletter']);
}
// Check for resend confirmation message (for thank you page)
if (isset($_SESSION['petition_resend_message'])) {
$vars['petition_resend_message'] = $_SESSION['petition_resend_message'];
unset($_SESSION['petition_resend_message']);
}
return $vars;
});