# Phase 1 - Content Bot Blog Engine

## Overview
This repo contains a PHP 8.x micro-app that:

- Generates a draft blog post plus an optional featured image for a given topic.
- Sends an approval email that links to sanitized preview and publish pages.
- Publishes approved drafts directly to a WordPress site via the REST API.

## Setup

1. Copy `.env.example` to `.env` and fill in the values. Pay attention to:
   - `APP_BASE_URL` (should end with `/public` so preview/approval links work).
   - `DB_DSN` pointing to an SQLite file (`sqlite:./database/blog_engine.sqlite` by default) plus `DB_USER`/`DB_PASS`.
   - `OPENAI_API_KEY`, `WP_USERNAME`, and a WordPress Application Password (`WP_APP_PASSWORD`; see the WordPress Integration section for steps).
   - Editorial guidance: toggle with `EDITORIAL_PASS_ENABLED`, adjust `EDITORIAL_MODEL`/`EDITORIAL_TEMPERATURE`, and drop narrative rules into `storage/brand_pack.txt` or `storage/voice_pack.txt` so the system prompt can absorb them.
   - Premium image controls (`IMAGE_MODEL`, `IMAGE_SIZE`, `IMAGE_QUALITY`, `IMAGE_N`) let you trade off fidelity and diversity. The defaults now target DALL-E 3 (dall-e-3 / 1024x1024 / standard / 1). DALL-E 3 supports 1024x1024, 1024x1792, and 1792x1024; other sizes gracefully fall back to 1024x1024.
   - Email recipients (`EMAIL_FROM`, `EMAIL_TO`) plus optional SMTP settings determine how the approval notification is delivered (SMTP takes precedence over `mail()`).
   - WordPress defaults (`WP_BASE_URL=https://mytown.ink`, `WP_DEFAULT_STATUS`, `WP_DEFAULT_CATEGORY_ID`, and `WP_DEFAULT_TAGS`) keep every publish consistent with your site conventions.
2. Create the SQLite file and schema:
   - `mkdir -p database` if needed and run `sqlite3 ./database/blog_engine.sqlite < sql/001_init.sql`.
3. Ensure the runtime can write to `storage/images`, `logs`, and any guidance files under `storage/`. The CLI will create `storage/images` recursively and continue without an image if writing fails (logs will note the situation).
4. Install PHP 8.x CLI plus the `curl` and `pdo_sqlite` extensions; the app has no Composer dependencies.

## Database

1. The schema in `sql/001_init.sql` creates:
   - `drafts`: tracks topics, metadata, content, and optional image paths.
   - `approvals`: stores tokens, expiry dates, and actions.
   - `wp_publish`: links drafts to WordPress IDs/URLs and records publish outcomes.
2. Run `sqlite3 ./database/blog_engine.sqlite < sql/001_init.sql` to initialize the tables; make sure the file is owned by the PHP user.

## Generating Drafts

```
php scripts/generate_blog.php --topic="Working at heights training checklist"
```

This command:

- Inserts a `pending_approval` draft row, calls OpenAI with a strict JSON schema, and harvests `title`, `slug`, `meta_description`, and `content_html`.
- Optionally runs an editorial pass (when `EDITORIAL_PASS_ENABLED=true`) that enriches `content_html`, reuses your brand/voice notes from `storage/brand_pack.txt` and `storage/voice_pack.txt`, and can improve the SEO title/meta text.
- Invokes OpenAI image generation for a featured image, honoring the premium settings in `.env`, then saves the PNG to `storage/images/{draft_id}.png` when the directory is available. DALL-E 3 only supports 1024x1024, 1024x1792, and 1792x1024, and the script defaults to 1024x1024 otherwise.
- Sends an approval email to `EMAIL_TO` with both the preview link and the approval link so you can review before publishing.

## Preview / Approval

- `public/preview.php?id={draft_id}` renders a sanitized version of the draft. Only h1-h3, p, ul/ol, li, strong/em, anchors, blockquotes, and br tags survive, and links are limited to `http`, `https`, `mailto`, or safe relative paths that start with `/` or `#`. Inline scripts, styles, and `javascript:` URIs are removed.
- Approval links visit `public/approve.php?token={token}`. The GET request shows the draft details and a Publish button; submitting the same token via POST performs the publish. Tokens expire after 7 days, so rerun the generation command when the approval link is too old.
- Even if no featured image exists, the publish step continues without it. Publish failures set `drafts.status` to `publish_failed`, store the error text in `wp_publish`, and surface the exception message on the approval page instead of marking the draft as a generic failure.

## Logging

- Actions are logged to `logs/blog_engine.log`.
- Generation failures set `drafts.status` to `failed`, while publish issues mark the draft as `publish_failed` and record the exception.

## WordPress Integration

- `WP_BASE_URL` now defaults to `https://mytown.ink`; `lib/WpPublisher.php` uses that base for `/wp-json/wp/v2/media` (featured image upload) and `/wp-json/wp/v2/posts` (post creation).
- Create an Application Password at WP Admin > Users > Profile > Application Passwords. Give it a descriptive label, copy the generated string, and save it as `WP_APP_PASSWORD` alongside `WP_USERNAME` in `.env`. The user needs the `publish_posts` capability.
- `WP_DEFAULT_STATUS` controls the default `status` field (defaults to `publish`), `WP_DEFAULT_CATEGORY_ID` can list one or more category IDs, and `WP_DEFAULT_TAGS` expects comma-separated WordPress tag IDs that are automatically applied to each post.
- Authentication uses HTTP Basic with the username/app password. When provided, featured image uploads attach the returned media ID via `featured_media`, and the `status`, `categories`, and `tags` fields respect the configured defaults so every publish conforms to your site conventions.
