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