system, and templates Add content system documentation Add newsletter plugin documentation Add petition system documentation Add templates documentation
139 lines
6.3 KiB
Markdown
139 lines
6.3 KiB
Markdown
# 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`:
|
|
```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
|
|
|
|
1. User fills form (name, email, region, display preference, GDPR consent)
|
|
2. Anti-spam checks: CSRF, honeypot, time-based, referrer, rate limiting (session + IP)
|
|
3. Signature saved to CSV with `status=pending` and confirmation `token`
|
|
4. Confirmation email sent via SMTP (with retry + exponential backoff)
|
|
5. User redirected to thank-you page
|
|
6. User clicks confirmation link -> `?confirm=TOKEN` -> status changes to `confirmed`
|
|
7. Thank-you email sent with delete link
|
|
8. 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 |
|
|
| email | 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
|
|
|
|
1. **CSRF token** - Session-based, validated on submit
|
|
2. **Honeypot field** - Hidden `website` field, rejects if filled
|
|
3. **Time check** - Rejects if form submitted in <3 seconds
|
|
4. **Referrer check** - Validates HTTP referrer matches host
|
|
5. **Session rate limit** - 1 submission per 60 seconds
|
|
6. **IP rate limit** - 3 attempts per 5 minutes per IP (stored in `custom/data/petition-rate-limit.csv`)
|
|
7. **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, see `smtp-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:
|
|
1. User enters email address
|
|
2. Plugin looks up pending signature by email
|
|
3. Generates new token, updates CSV
|
|
4. Sends new confirmation email
|
|
5. 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
|
|
|
|
1. **File locking** - All CSV operations use `flock()`. Never bypass.
|
|
2. **Duplicate check inside lock** - `petitionAppendSignature()` checks duplicates while holding exclusive lock to prevent race conditions.
|
|
3. **Token expiry** - 30 days (2592000 seconds). Do not change without updating email text.
|
|
4. **PRG pattern** - Form submission redirects via Post/Redirect/Get. Session stores errors/data.
|
|
5. **Privacy** - Resend flow never reveals whether an email exists in the system.
|
|
6. **CSV sanitization** - All user input goes through `petitionSanitizeCSV()` before writing.
|
|
|
|
## CLI Tool
|
|
|
|
`custom/petition-cli.php` - Run inside container:
|
|
```bash
|
|
podman exec stopplidelsen.no php /var/www/custom/petition-cli.php [command] [args]
|
|
```
|
|
|
|
Commands: list signatures, confirm by email, delete by email, resend confirmation.
|