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:
parent
fffd51422c
commit
c8efa479bc
7 changed files with 208 additions and 1 deletions
|
|
@ -1,4 +1,7 @@
|
||||||
<h1 id="sign-now">Signer her for å vise din støtte</h1>
|
<h1 id="sign-now">Signer her for å vise din støtte</h1>
|
||||||
|
|
||||||
<?= $petition_form ?? '' ?>
|
<?= $petition_form ?? '' ?>
|
||||||
|
|
||||||
|
<p class="text-small text-muted">Har du allerede signert, men ikke mottatt bekreftelsesmail? <a href="send-bekreftelse-pa-nytt/">Send bekreftelse på nytt</a></p>
|
||||||
|
|
||||||
<?= $petition_signatures ?? '' ?>
|
<?= $petition_signatures ?? '' ?>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<h1>Send bekreftelse på nytt</h1>
|
||||||
|
|
||||||
|
<p>Skriv inn e-postadressen du brukte da du signerte, så sender vi en ny bekreftelseslenke.</p>
|
||||||
|
|
||||||
|
<?php if (!empty($petition_resend_message)): ?>
|
||||||
|
<div class="form-message form-message--<?= htmlspecialchars($petition_resend_message['type']) ?>" role="alert">
|
||||||
|
<p><?= htmlspecialchars($petition_resend_message['text']) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="" class="resend-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="resend_email">E-post</label>
|
||||||
|
<input type="email" id="resend_email" name="resend_email" placeholder="din@epost.no" required maxlength="100">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" name="petition_resend" class="button">Send på nytt</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p><a href="../">Tilbake til underskriftskampanjen</a></p>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
title = "Send bekreftelse på nytt"
|
||||||
|
plugins = "petition-form"
|
||||||
|
petition_id = "medisinsk-cannabis-pa-resept"
|
||||||
|
petition_title = "Underskriftskampanje: Ja til medisinsk cannabis på resept!"
|
||||||
|
|
||||||
|
[en]
|
||||||
|
title = "Resend confirmation"
|
||||||
|
petition_title = "Petition: Yes to medical cannabis on prescription!"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@import url("../styles.css");
|
||||||
|
|
@ -8,4 +8,6 @@
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p>Fikk du ikke e-posten? <a href="../send-bekreftelse-pa-nytt/">Send bekreftelse på nytt</a></p>
|
||||||
|
|
||||||
<p><a href="../">Tilbake til underskriftskampanjen</a></p>
|
<p><a href="../">Tilbake til underskriftskampanjen</a></p>
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,14 @@ gdpr_consent_text = "Jeg har lest <a href=\"/personvern/\" target=\"_blank\">per
|
||||||
gdpr_consent_required = "Du må samtykke til personvernerklæringen for å signere."
|
gdpr_consent_required = "Du må samtykke til personvernerklæringen for å signere."
|
||||||
newsletter_subscribe = "Jeg ønsker å motta nyhetsbrev fra Stopp Lidelsen (omtrent 12 i året). Du kan melde deg av når som helst."
|
newsletter_subscribe = "Jeg ønsker å motta nyhetsbrev fra Stopp Lidelsen (omtrent 12 i året). Du kan melde deg av når som helst."
|
||||||
email_rights_info = "Du har rett til innsyn, retting og sletting av dine opplysninger. Kontakt oss på kontakt@stopplidelsen.no eller klag til Datatilsynet (datatilsynet.no)."
|
email_rights_info = "Du har rett til innsyn, retting og sletting av dine opplysninger. Kontakt oss på kontakt@stopplidelsen.no eller klag til Datatilsynet (datatilsynet.no)."
|
||||||
|
resend_title = "Fikk du ikke e-posten?"
|
||||||
|
resend_description = "Skriv inn e-postadressen du brukte da du signerte, så sender vi en ny bekreftelseslenke."
|
||||||
|
resend_email_placeholder = "din@epost.no"
|
||||||
|
resend_submit = "Send på nytt"
|
||||||
|
resend_success = "Hvis e-postadressen finnes i vårt system, har vi sendt en ny bekreftelseslenke."
|
||||||
|
resend_not_found = "Vi fant ingen ubekreftet signatur med denne e-postadressen. Kanskje du skrev feil da du signerte? Du kan gjerne prøve å signere på nytt."
|
||||||
|
resend_already_confirmed = "Denne signaturen er allerede bekreftet."
|
||||||
|
resend_rate_limit = "Vennligst vent litt før du ber om en ny e-post."
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
agder = "Agder"
|
agder = "Agder"
|
||||||
|
|
|
||||||
|
|
@ -799,6 +799,105 @@ function petitionGetSignatureByToken(string $csvPath, string $token): ?array {
|
||||||
return $signature;
|
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)
|
* Send thank you email with delete link (with retry wrapper)
|
||||||
*/
|
*/
|
||||||
|
|
@ -1136,7 +1235,8 @@ function petitionGetPageData(?Context $ctx): ?array {
|
||||||
|
|
||||||
$csvPath = petitionGetCsvPath($petitionId);
|
$csvPath = petitionGetCsvPath($petitionId);
|
||||||
// loadMetadata() already merges language-specific metadata via Hook::PROCESS_CONTENT
|
// 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';
|
$thankYouPage = $metadata['thank_you_page'] ?? 'takk';
|
||||||
|
|
||||||
$formErrors = [];
|
$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
|
// Handle form submission
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['petition_submit'])) {
|
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']);
|
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;
|
return $vars;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue