Contensio logo

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();