# 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.