Content approval
People who can edit pages or posts but not publish them send their drafts to a publisher, who publishes them or sends them back with a note. Every step is kept, and every page or post going live or being taken down is logged.
Pages and posts (the items of every collection) work the same way. This guide describes pages; Posts lists what differs.
Who can do what
One permission decides it for pages, publish wranglerpages, and one for
posts, publish posts.
| Person | Has | Can |
|---|---|---|
| Editor | update wranglerpages, not publish wranglerpages |
create and edit draft pages, submit them for review, withdraw a submission |
| Publisher | update wranglerpages and publish wranglerpages |
everything an editor can, and publish or take down pages, change live pages, approve submissions and ask for changes |
Super admins can do everything.
Upgrading changes nothing for a site: the migrations give publish wranglerpages
to every role that can update pages, and publish posts to every role that can
update posts. To turn approval on for a role, take the
permission away from it (Admin → Roles).
What editors see
- New pages they create start as drafts. The page's Status shows as text, not a menu.
- A draft page shows Submit for review, with an optional note for the publisher. While it waits, the page shows Waiting for review and a Withdraw button, and it can't be changed (nor its blocks): what the publisher decides on is what was sent. Withdrawing it makes it editable again. If a publisher asks for changes, their note shows on the page, and the editor can submit again.
- A live page opens read-only, with Edit a draft of this page. That makes a draft (a working copy) with the page's settings, tags and blocks, and opens it. Editors change the draft freely with the usual editor and page builder; nothing changes on the site. They submit it for review like a new page, or Discard draft. A live page with a draft shows Open the draft.
- The dashboard widget Your work in review lists their open submissions.
What publishers see
-
On a draft of a live page, Compare with the live page lists the settings that differ and each block added, changed, moved or removed, with previews of both (in the site's look when the site has a preview frame).
-
Approve & publish copies the draft over the live page: its settings, tags and blocks. The live page keeps its id, URL, place in the menu and status, and its blocks keep their ids and history (they take on the draft's content; blocks added in the draft are added, removed ones go to the trash). The draft is removed. If the live page changed after the draft was made, the publisher is warned and can Publish anyway.
-
A submission remembers what it looked like when it was sent. If a publisher changed it since, publishing warns and needs Publish anyway (under the two-person rule it has to be sent for review again instead).
-
A draft of a page that has been taken down can't be published over it: publish the page again first, or discard the draft. The page itself can still be edited and submitted.
-
Each decision (publish, schedule, request changes, unschedule) is taken once: a second click, or a scheduled publish at the same moment, is refused.
-
A publisher can also publish a draft nobody submitted; it is recorded as submitted and approved by them.
-
A page waiting for review shows who sent it, when, and two buttons: Approve & publish, and Request changes (which needs a note).
-
The dashboard widget Waiting for review lists every page and post waiting for someone who can publish it.
-
Publishers edit and publish pages directly, as before.
Scheduling
- Beside Approve & publish, a publisher can pick a date and time and Approve & schedule. The author is emailed. The panel, the dashboard widget and the live page's banner show when it goes live.
- Until then, people who can't publish can't change it (or a draft's blocks). The author can Withdraw it; a publisher can Unschedule it (back to review) or Publish now.
textstem:publish-due, run every minute by the package's schedule, publishes it as the publisher who scheduled it. If the live page changed since the draft was made, or that publisher can no longer publish, it isn't published: it goes back to review, with a note saying why, and publishers are emailed.- The site's scheduler has to be running (
php artisan schedule:runevery minute from cron) for scheduled publishing to happen.
The two-person rule
With TEXTSTEM_APPROVALS_TWO_PERSON=true, publishers can't approve, schedule
or send back their own submissions: another publisher has to. A publisher's own
drafts then show Submit for review like an editor's. Off by default.
A submission a publisher changed after it was sent can't be approved until it
is sent for review again, so another publisher sees those changes. Publishers
can still edit and publish live content directly.
Idle drafts
Drafts of live pages and posts that nobody has changed, and whose review
history hasn't moved, for TEXTSTEM_APPROVALS_COPY_DAYS days (90 by default)
are cleared by textstem:clear-idle-drafts, run daily at 04:00. The live page
or post is untouched. A week before, the people who worked on the draft (its
last editor and whoever submitted it) are emailed once. Drafts waiting for
review or scheduled are never cleared; a draft sent back with changes requested
is, and its review is recorded as withdrawn, with a "cleared" step. Set the
days to 0 to keep drafts for good; --dry-run lists what would happen.
Emails
Queued, and always in English:
- to every publisher (except the author) when a page or post is submitted, or when a scheduled one goes back to review;
- to the author when their page or post is published, scheduled, or sent back with a note;
- to the people who worked on a draft a week before it is cleared for being idle.
Records
textstem_reviews: each submission, its status (in_review,changes_requested,scheduled,published,withdrawn), who submitted and who decided, when it is scheduled to go live (publish_at), and the version that was published;content_signaturefingerprints what was sent (and, once scheduled, what was approved).textstem_review_events: every step (submitted, changes requested, scheduled, unscheduled, published, not published at its time, withdrawn, discarded, cleared, closed because the page or post was deleted), with who (none for steps the scheduler took), when and any note. Kept for the life of the page; the page editor shows them under Review history.- The events log gets a
publishedorunpublishedentry whenever a page or post goes live or is taken down, however it happens, with who did it (for a scheduled publication, the publisher who scheduled it), the version that went live and the review that approved it. These entries are kept when old events are pruned. - The version that went live is taken at that moment (whatever the snapshot
throttle) and marked
published_atinwrangler_page_versions/post_versions; those versions are kept when old versions are pruned.
Drafts of live pages
A draft is a hidden page (wrangler_pages.working_copy_of = the live page's id)
with copies of its blocks (wrangler_components.copied_from = the live block).
A global scope (WithoutWorkingCopies) hides drafts from every page query, so
they never appear in lists, menus, the sitemap, search, the router or the AI
tools; WranglerPage::withWorkingCopies() reaches them. A draft keeps its live
page's slug and URL, can never be made live itself, and is removed with its live
page (closing its submission). There is one draft per live page
(working_copy_of is unique); working_copy_by records who started it. working_copy_base fingerprints the live page when the draft was made, to
spot changes made to it since.
Posts
- The post editor has the same review panel, read-only view of live posts and Edit a draft of this post. Compare with the live post lists the settings that differ (posts have no blocks).
- A draft carries the post's title, slug, description, keywords, content,
picture, visibility, publish and unpublish dates, fields (
meta), category, author, location, area and tags. The live post keeps its collection, place in it and status. - Drafts are rows of
postswithworking_copy_ofset, hidden the same way as page drafts (Post::withWorkingCopies()reaches them), and never searchable. - The post policy's
updateanddeleteneedpublish postson a live post;editopens the editor without it.
Other content
Approval for a kind of content is a ContentApprovals subclass
(PageApprovals, PostApprovals): which column means live, which settings a
draft carries, its editor and comparison URLs, its versions, and anything that
belongs to it (a page's blocks). The model uses the HasWorkingCopies trait,
implements the Medialight\Textstem\Contracts\HasWorkingCopies contract, and
calls LiveContentGuard and Publications from its saving and saved events.
ContentApprovals::for($model) finds the right one.
How it is enforced
The rules hold however a change is made: the page editor, the page builder, table actions, the AI tools, the API and imports.
- The page policy's
updateanddeleteneedpublish wranglerpageson a live page, and so do the block policy'supdateanddelete. A neweditability opens the editor without it, read-only on live pages. - Underneath,
LiveContentGuardstops anyone signed in without the permission from making a page or post live, taking it down, or changing a live page (blocks included) or post, and makes the pages and posts they create drafts. Work done without anyone signed in (console commands, queued jobs) isn't affected.