A WordPress plugin that lets an editorial team track posts through a review workflow — assign a status, list what's pending, and update it — before publication.
Status: v0.1-rc. This is a checkpoint, not a finished release. See Known debt below for what's explicitly not done yet.
Every post moves through three custom sub-statuses while WordPress's own post_status is pending:
unassigned— no reviewer has looked at it yet (default on creation).reviewing— a reviewer has picked it up.approved— a reviewer signed off; the post can then be published, scheduled, or left in the queue.
The sub-status is stored as a non-public custom taxonomy, erq_review_statuses, with exactly one term assigned per post. The reasoning behind that choice (over post meta, a CPT, or a custom table) is written up in ADR.md.
Code lives under src/, namespace EditorialReviewQueue, autoloaded via Composer/PSR-4. Each class owns one concern:
| Class | Responsibility |
|---|---|
BootstrapManager |
Registers the taxonomy, seeds its three terms, and backfills unassigned onto any pre-existing pending post that has no status yet. |
QueryManager |
Read-only domain logic: list pending posts, fetch a single post by ID, check the current user's capability to review. Knows nothing about REST or the CLI. |
StatusManager |
Owns the valid status list (StatusManager::VALID_STATUSES) and the only place that changes a post's status. |
RestRegister |
Registers the REST routes and adapts REST-specific input (WP_REST_Request) into plain arguments for QueryManager/StatusManager. |
CliRegister |
Registers the WP-CLI erq command and adapts CLI-specific input ($args/$assoc_args) the same way. |
Both RestRegister and CliRegister are thin adapters over the same domain classes — REST and WP-CLI expose the same underlying data and actions through two different transports, not two different implementations.
ChecklistManager, NotesManager, ReviewsManager, and RoleManager exist as empty placeholders, matching sections already decided in the ADR (review checklist, reviewer notes, the Reviews CPT, and the custom reviewer role/capability) that are not implemented yet — see Known debt.
Namespace: editorial-review-queue/v1. All routes currently require the manage_options capability.
| Method | Route | Description |
|---|---|---|
GET |
/posts |
List all pending posts with their review status. |
GET |
/posts/{id} |
Get a single post's review status. 404 if the ID doesn't match a post. |
PATCH |
/posts/{id} |
Update a post's review status. Body: {"review_status": "reviewing"}. review_status is validated against the enum of valid statuses at the schema level (400 rest_not_in_enum if invalid); id is validated with a custom validate_callback (400 rest_invalid_param if not a real post). |
Errors are returned as standard WP_Error responses with an appropriate HTTP status, not raw PHP fatals.
wp erq get_posts # table of all pending posts
wp erq get_post <id> # single post, or an observable error if invalid/missing
wp erq update_post <id> --review-status=<status> # updates the status; exits non-zero on invalid id or statusFailures in QueryManager::get_single_post() and StatusManager::update_single_post_status() are recorded via PHP's error_log() with the post ID and/or requested status — never the reviewer's internal note or any personal data.
- No automated tests, static analysis, or CI yet. Everything so far has been verified manually (Postman for REST,
wp erq ... --debugfor CLI). - Checklist, reviewer notes, the Reviews CPT, and the dedicated reviewer role/capability are designed in the ADR but not built.
check_user_can_review()currently gates everything onmanage_optionsas a placeholder. - Validation is intentionally duplicated in places (e.g. REST's
argsschema and a manual check inside the callback) as a deliberate defense-in-depth choice, not an oversight — but it means the same rule has to be kept in sync in two places if it ever changes. - No versioning/compatibility matrix yet (minimum/current PHP and WordPress versions, upgrade path).
- No packaged release/zip — only runnable from a working copy with
composer install.