WorkflowService
Static helper for querying and controlling the content approval workflow state at runtime.
Contensio\Services\WorkflowService is a static class that wraps all runtime checks for the content approval workflow. Use it in plugins, hooks, and custom controllers instead of reading settings directly.
Import
use Contensio\Services\WorkflowService;
Methods
isEnabled(): bool
Returns true when the content approval workflow is active. Reads from the workflow.enabled setting and caches the result for the duration of the request.
if (WorkflowService::isEnabled()) {
// workflow is active
}
autoPublishOnApproval(): bool
Returns true when approved content should be automatically published. Corresponds to the Auto-publish on approval toggle in Settings → Content.
if (WorkflowService::autoPublishOnApproval()) {
$content->update(['status' => 'published']);
}
canApprove(User $user): bool
Returns true when the given user can access the review queue and approve or reject submissions.
Admins and Super Admins always return true. Other users must have the content.approve permission.
if (WorkflowService::canApprove(auth()->user())) {
// show the review queue link
}
canBypassReview(User $user): bool
Returns true when the given user can publish content directly without going through the review queue.
Admins and Super Admins always return true. Other users must have the content.bypass_review permission.
if (! WorkflowService::canBypassReview(auth()->user())) {
// force draft status before saving
$request->merge(['status' => 'draft']);
}
pendingCount(): int
Returns the count of content items currently in review_status = 'pending'. Useful for sidebar badges and dashboard widgets.
$badge = WorkflowService::pendingCount();
The result is not cached — each call queries the database. Call it once per request and store the result.
flush(): void
Clears the request-level cache so the next call to isEnabled() (or any method that reads settings) re-fetches from the database.
WorkflowService::flush();
Useful after changing workflow settings in a test or after programmatically updating settings.
Review statuses
The review_status column on the contents table is a nullable string. The Content model exposes constants:
| Constant | Value | Meaning |
|---|---|---|
Content::REVIEW_PENDING |
'pending' |
Submitted; awaiting a reviewer decision |
Content::REVIEW_APPROVED |
'approved' |
Accepted; published if auto-publish is on |
Content::REVIEW_SOFT_REJECTED |
'soft_rejected' |
Revision requested; author can resubmit |
Content::REVIEW_HARD_REJECTED |
'hard_rejected' |
Permanently rejected; no resubmission |
null means the content has never been submitted for review (or the workflow was not active when it was created).
Content model helpers
Content exposes two helper methods:
canBeSubmittedForReview(): bool
Returns true when the content item is eligible to enter the review queue. Content must be in draft status and must not be already pending or hard-rejected.
if ($content->canBeSubmittedForReview()) {
$content->update([
'review_status' => Content::REVIEW_PENDING,
'review_requested_at' => now(),
]);
}
isUnderReview(): bool
Returns true when review_status is pending.
Hooks
The workflow fires standard Contensio action hooks you can listen to from a plugin:
| Hook | When | Parameters |
|---|---|---|
contensio/review/submitted |
Author submits for review | Content $content |
contensio/review/approved |
Reviewer approves | Content $content, User $reviewer |
contensio/review/rejected |
Reviewer rejects (soft or hard) | Content $content, User $reviewer, string $type, ?string $notes |
Example — send a Slack notification when content is submitted:
add_action('contensio/review/submitted', function (Content $content) {
Http::post(config('services.slack.webhook'), [
'text' => "New review request: {$content->title} by {$content->author->name}",
]);
});
Audit log
Every review action is written to the content_review_log table via the ContentReviewLog model. The model has UPDATED_AT = null — rows are append-only and cannot be modified after creation.
Schema:
| Column | Type | Description |
|---|---|---|
id |
bigint | Auto-increment primary key |
content_id |
bigint | FK → contents.id |
user_id |
bigint | The reviewer (or author for submissions) |
action |
string | submitted, approved, soft_rejected, hard_rejected |
notes |
text (nullable) | Reviewer notes — always null for approvals |
created_at |
timestamp | When the action occurred |
Query the log for a specific content item:
use Contensio\Models\ContentReviewLog;
$log = ContentReviewLog::where('content_id', $content->id)
->with('user')
->latest()
->get();