system, and templates Add content system documentation Add newsletter plugin documentation Add petition system documentation Add templates documentation
6.3 KiB
Petition System Reference
For LLM agents working on the petition form. Read when modifying petition functionality.
Overview
GDPR-compliant petition system with double opt-in email confirmation. File-based storage (CSV) with proper locking. Located in custom/plugins/page/petition-form.php.
How It Activates
Add to a page's metadata.ini:
plugins = "petition-form"
petition_id = "my-petition" # Optional, defaults to folder slug
thank_you_page = "takk" # Subpage slug for post-submission redirect
The plugin is a page plugin (not global). It's loaded by the framework when the page is rendered, making its functions available to PHP content files.
User Flow
- User fills form (name, email, region, display preference, GDPR consent)
- Anti-spam checks: CSRF, honeypot, time-based, referrer, rate limiting (session + IP)
- Signature saved to CSV with
status=pendingand confirmationtoken - Confirmation email sent via SMTP (with retry + exponential backoff)
- User redirected to thank-you page
- User clicks confirmation link ->
?confirm=TOKEN-> status changes toconfirmed - Thank-you email sent with delete link
- User can delete signature via
?delete=TOKEN(GDPR right to erasure)
CSV Format
File: custom/data/petitions/{petition_id}.csv
timestamp,email,firstname,surname,region,display,status,token,token_created,ip_hash
| Column | Description |
|---|---|
| timestamp | Unix timestamp of submission |
| Sanitized email (CSV injection protected) | |
| firstname | Sanitized first name |
| surname | Sanitized last name |
| region | Region key (e.g., oslo, vestland) |
| display | Privacy preference: anonymous, semi, full |
| status | pending or confirmed |
| token | 64-char hex confirmation/delete token |
| token_created | Unix timestamp for 30-day expiry |
| ip_hash | SHA-256 of IP + petition_id |
Key Functions
| Function | Purpose |
|---|---|
petitionGetPageData(?Context) |
Main entry point. Returns all petition data for template rendering. Handles GET confirm/delete and POST submission. |
petitionAppendSignature(csvPath, data) |
Atomic append with file locking + duplicate check inside lock |
petitionConfirmSignature(csvPath, token) |
Confirms pending signature, checks 30-day token expiry |
petitionDeleteSignature(csvPath, token) |
GDPR deletion - removes row entirely |
petitionEmailExists(csvPath, email) |
Duplicate check (case-insensitive) |
petitionGetConfirmedSignatures(csvPath) |
Returns confirmed signatures sorted newest-first |
petitionRenderForm(ctx, formData, errors, showForm) |
Generates form HTML |
petitionRenderSignatures(signatures, ctx) |
Generates signature list HTML |
petitionSendConfirmationEmail(...) |
Sends confirmation with retry wrapper |
petitionSendThankYouEmail(...) |
Sends thank-you with delete link |
petitionCheckIPRateLimit(id, max, window) |
IP-based rate limiting (separate CSV file) |
petitionT(ctx, section, key, replacements) |
Translation helper for petition strings |
petitionGetPendingSignatureByEmail(csvPath, email) |
Lookup for resend functionality |
petitionUpdateSignatureToken(csvPath, email, newToken) |
Token refresh for resend |
Anti-Spam Measures
- CSRF token - Session-based, validated on submit
- Honeypot field - Hidden
websitefield, rejects if filled - Time check - Rejects if form submitted in <3 seconds
- Referrer check - Validates HTTP referrer matches host
- Session rate limit - 1 submission per 60 seconds
- IP rate limit - 3 attempts per 5 minutes per IP (stored in
custom/data/petition-rate-limit.csv) - CSV injection prevention - Prefixes dangerous characters with
'
Email System
- Uses PHPMailer.Lite (
custom/vendor/PHPMailer.Lite.php) - Config in
custom/smtp-config.php(not in repo, seesmtp-config.php.example) - Supports petition-specific SMTP overrides in config
- Retry with exponential backoff: 3 attempts at 2s, 4s, 8s delays
- All sends logged to
custom/data/smtp-log.csv - Pre-flight TCP connection check before attempting send
Resend Confirmation Flow
Subpage at send-bekreftelse-pa-nytt/ allows users to request a new confirmation email:
- User enters email address
- Plugin looks up pending signature by email
- Generates new token, updates CSV
- Sends new confirmation email
- Always shows generic message (privacy: doesn't reveal if email exists)
Content Structure
content/underskriftskampanje/medisinsk-cannabis-pa-resept/
metadata.ini # plugins="petition-form", petition_id, thank_you_page
index.php # Calls petitionGetPageData(), renders form + signatures
takk/
metadata.ini # plugins="petition-form" (needed for context)
index.php # Thank-you page with check-email message
send-bekreftelse-pa-nytt/
metadata.ini # plugins="petition-form", petition_id, petition_title
index.php # Resend confirmation form
Translation Keys
All petition strings use petition.* prefix in language files. Key groups:
- Form labels:
petition.firstname_label,petition.email_label, etc. - Validation:
petition.firstname_required,petition.email_required, etc. - Display options:
petition.display_semi,petition.display_anonymous,petition.display_full - Email content:
petition.email_greeting,petition.email_subject, etc. - Confirmation:
petition.confirm_success,petition.confirm_expired, etc. - Regions:
regions.oslo,regions.vestland, etc.
Critical: Do Not Break
- File locking - All CSV operations use
flock(). Never bypass. - Duplicate check inside lock -
petitionAppendSignature()checks duplicates while holding exclusive lock to prevent race conditions. - Token expiry - 30 days (2592000 seconds). Do not change without updating email text.
- PRG pattern - Form submission redirects via Post/Redirect/Get. Session stores errors/data.
- Privacy - Resend flow never reveals whether an email exists in the system.
- CSV sanitization - All user input goes through
petitionSanitizeCSV()before writing.
CLI Tool
custom/petition-cli.php - Run inside container:
podman exec stopplidelsen.no php /var/www/custom/petition-cli.php [command] [args]
Commands: list signatures, confirm by email, delete by email, resend confirmation.