# UTM Builder + AI Labels (utm-builder-ai-labels)

Build consistent UTM URLs without spreadsheet wrangling. This one-page PHP app assembles UTM links, enforces your taxonomy, optionally names campaigns with AI, and can shorten links.

## Features
- UTM builder inputs: Landing URL, Source, Medium, Campaign, Term, Content
- Taxonomy enforcement with normalization and lowercase parameter option
- AI campaign naming and label suggestions (temperature 0.2), optional
- History of generated links with copy-to-clipboard
- Optional link shortener (Bitly, Rebrandly, TinyURL) and custom domain
- Export history to CSV and JSON
- Secure by default: strict CSP, CSRF protection, prepared statements, output escaping
- Built-in rate limiting (30 requests/minute)
- Light theme, purple accent, split layout
- Optional cron for cleanup and quotas
- Freemium quotas: 50 free / 500 pro per month (configurable)

## Requirements
- PHP 8.2+ with extensions: pdo_mysql, curl, mbstring, json, openssl
- MySQL 8.x
- HTTPS in production

## Quick Start
1) Database
- Create database and user: `CREATE DATABASE utm_app CHARACTER SET utf8mb4;` and grant privileges.
- Initialize schema with the included tables: runs, rate_limits, links, taxonomies, settings (import schema.sql if provided).

2) Configure environment
- Create a `.env` (or set real environment variables) with DB credentials, app secret, and optional AI/shortener keys. See the configuration table below.

3) Launch locally
- From the project root, run: `php -S 127.0.0.1:8080`
- Visit: http://127.0.0.1:8080

4) Deploy
- Use Nginx/Apache + PHP-FPM 8.2+, behind TLS.
- Set `APP_ENV=production`, disable `display_errors`, enable OPcache.
- Ensure process can read environment variables and reach MySQL.
- Confirm security headers are present (see Security).

## Configuration (environment variables)
| Variable | Description | Default | Required |
|---|---|---|---|
| APP_ENV | Environment: production or development | production | Yes |
| APP_BASE_URL | Public base URL of the app | — | Yes (prod) |
| APP_SECRET | Random 32+ char secret for CSRF/session HMAC | — | Yes |
| APP_TIMEZONE | PHP timezone | UTC | No |
| APP_RATE_LIMIT_PER_MINUTE | Requests per minute (per IP/session) | 30 | No |
| APP_PLAN | monetization plan: freemium or unlimited | freemium | No |
| APP_FREE_QUOTA | Monthly free quota (runs) | 50 | No |
| APP_PRO_QUOTA | Monthly pro quota (runs) | 500 | No |
| APP_LOWERCASE_PARAMS_DEFAULT | Default for “Lowercase Params” UI | true | No |
| APP_ENFORCE_TAXONOMY_DEFAULT | Default for “Enforce Taxonomy” UI | true | No |
| APP_AI_LABELS_DEFAULT | Default for “AI Names & Labels” UI | true | No |
| APP_CSP | Override CSP header (see Security) | (secure default) | No |
| APP_DB_HOST | MySQL host | 127.0.0.1 | Yes |
| APP_DB_PORT | MySQL port | 3306 | No |
| APP_DB_NAME | MySQL database name | utm_app | Yes |
| APP_DB_USER | MySQL username | — | Yes |
| APP_DB_PASS | MySQL password | — | Yes |
| APP_DB_CHARSET | MySQL charset | utf8mb4 | No |
| OPENAI_API_KEY | OpenAI API key (if AI features used) | — | Cond. |
| OPENAI_MODEL | OpenAI model for labels/naming | gpt-4-turbo | No |
| OPENAI_TEMPERATURE | Model temperature | 0.2 | No |
| OPENAI_TIMEOUT_MS | HTTP timeout to OpenAI in ms | 12000 | No |
| APP_SHORTENER_PROVIDER | none, bitly, rebrandly, tinyurl | none | No |
| APP_SHORTENER_DOMAIN | Custom short domain (if provider supports) | — | No |
| BITLY_TOKEN | Bitly token (if provider=bitly) | — | Cond. |
| REBRANDLY_API_KEY | Rebrandly API key (if provider=rebrandly) | — | Cond. |
| TINYURL_TOKEN | TinyURL API token (if provider=tinyurl) | — | Cond. |
| APP_CRON_TOKEN | Secret token to authorize cron endpoint | — | No |

Notes:
- Set OPENAI_* only if you want AI naming/labels.
- If using a shortener, set the corresponding provider token and optional custom domain.
- APP_SECRET must be unique per deployment.

## How it works
- Inputs: landing_url (required), campaign_info (notes), utm_source, utm_medium, utm_campaign, utm_term, utm_content, optional toggles for lowercase, taxonomy enforcement, AI labels, and shortener selection.
- Outputs: UTM URL, optional short URL, AI campaign name, labels, taxonomy status (ok/warn/fail), and a history list stored in MySQL.
- Taxonomy: sources and mediums are validated against the allowed sets and normalized. Unknowns trigger warn/fail and AI (if enabled) suggests fixes.
- Defaults: Lowercase parameters and taxonomy enforcement are on by default; AI labels default on if OPENAI_API_KEY exists.

## AI behavior and fallback
- With AI enabled: The app sends context (campaign_info + selected parameters) to OpenAI, requests a concise campaign name, label set, and normalization guidance at temperature 0.2. Suggestions respect the defined taxonomy and lowercase preference.
- With AI disabled: The app runs fully offline. It uses local taxonomy rules and the taxonomies table to normalize or warn. Users enter or adjust names manually.
- On AI errors/timeouts: The app degrades gracefully to offline mode for that run, logs the error in runs, preserves user input, and continues without blocking.

## Link shortener (optional)
- Providers: Bitly, Rebrandly, TinyURL.
- Configure `APP_SHORTENER_PROVIDER` and the corresponding token. Optionally set `APP_SHORTENER_DOMAIN`.
- When enabled, the app creates a short link from the generated UTM URL and stores it in the links table. Failures are non-fatal and displayed as a warning.

## Exports
- In-app: Use Export CSV or Export JSON from the History panel.
- Programmatic: `GET /export.csv` or `GET /export.json` returns recent runs for the current session/user. Optional query params: `limit` (default 100, max 1000), `from`/`to` (ISO-8601). Responses respect quotas and rate limits.

## Security
- Content Security Policy (CSP): Strict default, blocking third-party scripts. Connect allowed only to the app origin and APIs you enable (OpenAI and chosen shortener). Customize via `APP_CSP` if needed.
- CSRF protection: All mutating requests include a token tied to `APP_SECRET`, origin-checked with SameSite cookies.
- Database safety: All queries use prepared statements with bound parameters.
- Output escaping: HTML output is encoded and attributes sanitized.
- Rate limiting: 30 requests/minute per IP/session. Excess returns HTTP 429 with Retry-After.
- Additional headers: X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: minimal.
- HTTPS recommended; disable `display_errors` in production.

## Storage
- Tables
  - runs: each build attempt, inputs, outputs, status, timing, AI usage flag
  - rate_limits: sliding window counters
  - links: short-link records and provider metadata
  - taxonomies: allowed values, synonyms, and mappings
  - settings: instance-level preferences (plan, quotas, UI defaults)
- Data retention: configurable via cron (below). Exports draw from runs.

## Cron (optional)
- Purpose: purge old runs and rate limit buckets, rotate logs, enforce monthly quotas.
- Two options:
  - HTTP: Schedule `curl -fsS https://your-app/_cron/cleanup?token=APP_CRON_TOKEN` daily.
  - CLI: Run a maintenance script if provided by your deploy process.
- Safe to omit; the app functions without cron, but tables may grow.

## Taxonomy details
- Default allowed values:
  - utm_source: google, facebook, instagram, linkedin, twitter, tiktok, email, newsletter, display, affiliate, referral, organic, other
  - utm_medium: cpc, social, email, display, affiliate, referral, organic, none
- Normalization: lowercase, trims spaces, maps common synonyms (e.g., fb → facebook). Unknowns flagged as warn/fail; AI suggests compliant alternatives when enabled.

## UI notes
- Light theme with purple accent and split layout: inputs on the left, preview/history on the right.
- Copy to clipboard buttons for UTM URL and short URL.
- History shows recent runs with quick export.

## Acceptance checklist
- [ ] PHP 8.2+ with pdo_mysql and curl; MySQL 8 reachable
- [ ] Database created and schema initialized (runs, rate_limits, links, taxonomies, settings)
- [ ] `.env` configured with APP_SECRET, DB settings, and APP_BASE_URL
- [ ] Security headers present; CSP blocks third-party scripts except configured APIs
- [ ] CSRF tokens included on POST; requests without token are rejected
- [ ] All DB interactions use prepared statements; no dynamic SQL concatenation
- [ ] Rate limiting enforces 30 req/min; 429 emitted when exceeded
- [ ] UTM builder enforces taxonomy; lowercase option works
- [ ] AI naming/labels produce results when OPENAI_API_KEY is set; fallback works on failure
- [ ] Shortener works for the selected provider; failures are non-fatal
- [ ] History persists runs and displays as expected
- [ ] Export CSV and JSON produce correct fields and encodings
- [ ] Optional cron cleans old runs/rate limits without errors
- [ ] Freemium quotas (50/500) enforced if APP_PLAN=freemium
- [ ] Copy-to-clipboard works across major browsers
- [ ] App runs over HTTPS; no sensitive data in URLs beyond UTM params

## License
MIT (c) You/Your Company. Replace with your organization’s preferred license if needed.