# Support Desk

A self-hosted, **rebrandable PHP technical support helpdesk** with multi-agent
ticketing, a searchable knowledge base, full-WYSIWYG article editing with
inline image uploads, and **email piping** — customers reply to your support
inbox and their replies are parsed and threaded onto the right ticket.

Built on plain PHP 8.1+, SQLite by default (MySQL supported), Twig, PHPMailer,
webklex/php-imap, and TinyMCE.

---

## Features

### Ticketing
- Multiple agents and admins with role-based permissions
- Per-ticket status (`open`, `pending`, `on_hold`, `resolved`, `closed`),
  priority, department, and assignee
- Internal notes (visible to staff only)
- File attachments (per reply)
- Customer portal for signed-in users
- Guest tickets (token-protected URL emailed to the customer — no account needed)
- Filtering, search, dashboard stats

### Email piping
- **Outbound** notifications via SMTP (PHPMailer) — both confirmation and
  reply notifications carry a `Reply-To` of your support inbox
- **Inbound** parsing via IMAP — `php bin/fetch-mail.php` polls unread mail and:
  1. Threads by subject token `[Ticket #T-123456]`
  2. Falls back to `In-Reply-To` and `References` against logged outbound
     `Message-ID`s and prior reply IDs
  3. Otherwise creates a **new ticket** from the email
- Quoted-reply portions are stripped (Gmail/Outlook/Apple markers + plain
  text "On … wrote:" preambles)
- Auto-responses and bulk mailings are detected and skipped to avoid loops
- Inbound attachments are saved alongside the reply
- Department routing: incoming mail addressed to `billing@yours.com` is
  routed to whichever department uses that address

### Knowledge base
- Categories with sort order, descriptions, icons
- **Full WYSIWYG editing** (TinyMCE) with **inline image uploads** —
  drag, paste, or browse; images are stored under `public/uploads/yyyy/mm/`
- HTMLPurifier sanitises every saved article so users can't slip XSS in
- Slugged URLs, view counts, helpful/unhelpful votes
- **Full-text search** via SQLite FTS5 when available, with a LIKE fallback
- Public published / draft modes
- Snippet highlighting in search results

### Branding
- Brand name, tagline, logo, primary color, footer, support email — all
  editable from the admin Settings page (or seeded from `.env`)
- Logos can be URL-referenced or uploaded
- The primary color flows through every page via a CSS custom property

---

## Requirements

- **PHP 8.1+** with extensions: `pdo_sqlite` (or `pdo_mysql`), `mbstring`,
  `fileinfo`, `json`, `openssl` (for SMTP/IMAP TLS), `imap` (for IMAP)
- **Composer**
- A web server (Apache/nginx) or use the bundled PHP dev server
- An SMTP outbox and IMAP inbox if you want email piping (Gmail with an app
  password, Mailgun, Postmark, your own server, etc.)

---

## Installation

```bash
git clone <this-repo> helpdesk
cd helpdesk
composer install
cp .env.example .env
# edit .env to set DB, SMTP, IMAP, and ADMIN credentials
php bin/install.php
composer serve   # http://localhost:8000
```

The installer:
1. Creates `data/support.sqlite` (or connects to your MySQL)
2. Runs every schema migration idempotently
3. Seeds default settings, the `support` department, and a sample KB article
4. Creates the admin user from `ADMIN_*` env vars or interactive prompts

Default admin URL: `/admin/login`. Default customer portal: `/portal/login`.

---

## Email setup

### Outbound (SMTP)

Set the `SMTP_*` variables in `.env`. Common providers:

| Provider   | Host                 | Port | Encryption |
|-----------|----------------------|------|------------|
| Gmail (app password) | smtp.gmail.com    | 587 | tls |
| Mailgun   | smtp.mailgun.org    | 587  | tls        |
| Postmark  | smtp.postmarkapp.com | 587 | tls        |
| Your own  | mail.your.com       | 587  | tls        |

Test from **Admin → Settings → Email tools**.

### Inbound (IMAP)

Set the `IMAP_*` variables in `.env`. The address you connect to should be
the same one you put in `MAIL_FROM_ADDRESS` / `BRAND_SUPPORT_EMAIL` so reply
threads work. Then schedule the cron:

**Linux:**
```cron
* * * * * cd /var/www/helpdesk && /usr/bin/php bin/fetch-mail.php >> storage/logs/fetch.log 2>&1
```

**Windows Task Scheduler:**
- Program: `php.exe`
- Arguments: `D:\software\support\bin\fetch-mail.php`
- Trigger: Daily, recur every 1 minute

You can also click **Fetch incoming mail now** in admin Settings to run it
on demand.

---

## Project structure

```
config/         configuration loader (reads $_ENV)
bin/            CLI scripts (installer, mail-fetch cron)
public/         web root (front controller, assets, uploads)
src/            PSR-4 autoloaded application code
  Controllers/  HTTP handlers
  Models/       database access (PDO)
  Mail/         SMTP send + IMAP parse
  App.php       kernel: routing, view, container
  Database.php  thin PDO wrapper
  Auth.php      session auth + middleware
  Migrations.php   schema + seed
views/          Twig templates
data/           SQLite DB lives here
storage/        cache (Twig, HTMLPurifier) + logs
```

---

## Security notes

- **CSRF**: every POST is validated against a per-session token. Forms get
  `{{ csrf_field()|raw }}`; AJAX uses `csrf_token()`.
- **HTMLPurifier**: WYSIWYG content is sanitised on save (and again on
  render via the `|md` filter for defence in depth). Two profiles:
  `strict()` (no images, used for ticket replies) and `rich()` (images,
  tables, used for KB articles).
- **Uploads**: file size + MIME enforced; uploads directory has an
  `.htaccess` denying script execution. Use nginx? Add the equivalent
  `location ~ \.php$ { deny all; }` block.
- **Sessions**: `HttpOnly`, `SameSite=Lax`, `Secure` when `APP_URL` is HTTPS.
- **Passwords**: `password_hash()` + `password_verify()`.
- **SQL**: prepared statements throughout. No string interpolation into queries.
- **Auto-responses**: inbound parser ignores messages with `Auto-Submitted`,
  `Precedence: bulk`, `X-Autoreply`, etc. to avoid loops.
- **DB indices** on hot paths (`tickets.status`, `ticket_replies.ticket_id`,
  `email_log.message_id`, `kb_articles.published`).

### Production deployment

Point your web server's document root at **`public/`**. Apache users get the
included `.htaccess` for free. For nginx:

```nginx
location / {
    try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ^~ /uploads/ {
    location ~ \.php$ { deny all; }
}
```

Set `APP_DEBUG=false` and `APP_ENV=production` in `.env`. The Twig cache
warms automatically into `storage/cache/`.

---

## Composer packages used

- [twig/twig](https://twig.symfony.com/) — templating
- [phpmailer/phpmailer](https://github.com/PHPMailer/PHPMailer) — SMTP send
- [webklex/php-imap](https://github.com/Webklex/php-imap) — IMAP fetch + MIME parse
- [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) — env loader
- [ezyang/htmlpurifier](https://github.com/ezyang/htmlpurifier) — WYSIWYG sanitiser
- [league/html-to-markdown](https://github.com/thephpleague/html-to-markdown) — HTML→text fallback for emails
- [ramsey/uuid](https://github.com/ramsey/uuid)

Frontend: TinyMCE 6 via CDN (no build step).
