Add newsletter subscription to petition form

Add Listmonk integration for newsletter subscriptions
Add newsletter checkbox to form
Handle newsletter subscription on form submission
Add session flag for thank you page
Remove duplicate sign-now ID from form section
This commit is contained in:
Ruben 2026-01-16 21:43:48 +01:00
parent afeeb658de
commit 054c59f177
3 changed files with 84 additions and 9 deletions

View file

@ -42,6 +42,61 @@ function petitionSanitizeCSV(string $value): string {
return $value;
}
/**
* Subscribe email to Listmonk newsletter lists via public API
* Listmonk handles double opt-in (sends its own confirmation email)
*/
function petitionSubscribeToNewsletter(string $email, string $name): bool {
$configPath = dirname(__DIR__, 2) . '/listmonk-config.php';
if (!file_exists($configPath)) {
error_log("Listmonk config not found: {$configPath}");
return false;
}
$config = require $configPath;
if (!$config['enabled']) {
error_log("Listmonk disabled in config");
return false;
}
// Log the UUIDs being used
error_log("Listmonk attempting subscription with UUIDs: " . implode(', ', $config['list_uuids']));
$payload = json_encode([
'email' => $email,
'name' => $name,
'list_uuids' => $config['list_uuids']
]);
$url = $config['url'] . '/api/public/subscription';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
error_log("Listmonk curl error: {$curlError}");
return false;
}
if ($httpCode < 200 || $httpCode >= 300) {
error_log("Listmonk subscription failed: HTTP {$httpCode}, URL: {$url}, Response: {$response}");
return false;
}
error_log("Listmonk subscription success for {$email}");
return true;
}
/**
* Check IP-based rate limiting to prevent email bombing and spam
* Returns true if IP is allowed, false if rate limit exceeded
@ -193,15 +248,15 @@ function petitionGetCsvPath(string $petitionId): string {
function petitionGetIdFromPath(string $pageDir): ?string {
// Get the last directory segment as the petition ID
$petitionSlug = basename($pageDir);
// Sanitize: remove any leading numeric prefix (e.g., "01-" becomes just the slug part)
// This handles folders like "01-my-petition" -> "my-petition"
$petitionSlug = preg_replace('/^\d+-/', '', $petitionSlug);
if (empty($petitionSlug)) {
return null;
}
return $petitionSlug;
}
@ -795,7 +850,7 @@ function petitionRenderForm(Context $ctx, array $formData, array $errors, bool $
$currentTime = time();
$regions = petitionGetRegions($ctx);
$html = '<section id="sign-now" class="petition-form">';
$html = '<section class="petition-form">';
// $html .= '<h2>' . htmlspecialchars(petitionT($ctx, 'petition', 'form_title')) . '</h2>';
// Error messages
@ -888,13 +943,18 @@ function petitionRenderForm(Context $ctx, array $formData, array $errors, bool $
$html .= '<a href="' . htmlspecialchars($privacyUrl) . '" target="_blank">' . htmlspecialchars(petitionT($ctx, 'petition', 'privacy_policy_link')) . '</a></p>';
$html .= '</div>';
// GDPR Consent checkbox
// GDPR Consent checkbox + Newsletter subscription
$consentChecked = isset($formData['gdpr_consent']) && $formData['gdpr_consent'] === 'on' ? ' checked' : '';
$newsletterChecked = isset($formData['newsletter']) && $formData['newsletter'] === 'on' ? ' checked' : '';
$html .= '<div class="form-group info-box info-box--green consent-group">';
$html .= '<label class="consent-label">';
$html .= '<input type="checkbox" name="gdpr_consent" id="gdpr_consent" required' . $consentChecked . '> ';
$html .= '<span>' . petitionT($ctx, 'petition', 'gdpr_consent_text') . ' <span class="required">*</span></span>';
$html .= '</label>';
$html .= '<label class="consent-label">';
$html .= '<input type="checkbox" name="newsletter" id="newsletter"' . $newsletterChecked . '> ';
$html .= '<span>' . htmlspecialchars(petitionT($ctx, 'petition', 'newsletter_subscribe')) . '</span>';
$html .= '</label>';
$html .= '</div>';
// Submit button
@ -1001,7 +1061,7 @@ function petitionGetPageData(?Context $ctx): ?array {
if (!$petitionId) {
return null;
}
$csvPath = petitionGetCsvPath($petitionId);
// loadMetadata() already merges language-specific metadata via Hook::PROCESS_CONTENT
$petitionTitle = $metadata['title'] ?? $petitionId;
@ -1186,7 +1246,16 @@ function petitionGetPageData(?Context $ctx): ?array {
// Send confirmation email
if (petitionSendConfirmationEmail($signatureData, $confirmUrl, $petitionTitle, $ctx)) {
// Subscribe to newsletter if opted in (fire and forget - don't block petition)
$newsletterOptIn = isset($_POST['newsletter']) && $_POST['newsletter'] === 'on';
error_log("Newsletter checkbox: " . ($newsletterOptIn ? 'checked' : 'not checked'));
if ($newsletterOptIn) {
$fullName = $formData['firstname'] . ' ' . $formData['surname'];
petitionSubscribeToNewsletter($formData['email'], $fullName);
}
$_SESSION['last_petition_submit'] = time();
$_SESSION['petition_subscribed_newsletter'] = $newsletterOptIn;
// Regenerate session ID to prevent session fixation
session_regenerate_id(true);
@ -1208,7 +1277,7 @@ function petitionGetPageData(?Context $ctx): ?array {
if (!empty($formErrors)) {
$_SESSION['petition_errors'] = $formErrors;
$_SESSION['petition_form_data'] = $formData;
$langPrefix = $ctx->get('langPrefix', '');
$currentPath = trim($ctx->requestPath, '/');
$redirectUrl = "{$langPrefix}/{$currentPath}/#sign-now";
@ -1254,5 +1323,11 @@ Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
$vars['petition_signatures'] = $data['signatures'];
}
// Check for newsletter subscription flag (for thank you page)
if (isset($_SESSION['petition_subscribed_newsletter'])) {
$vars['petition_subscribed_newsletter'] = $_SESSION['petition_subscribed_newsletter'];
unset($_SESSION['petition_subscribed_newsletter']);
}
return $vars;
});