*/ public const VALID_STATUSES = [ self::STATUS_DRAFT, self::STATUS_PUBLISHED, self::STATUS_ARCHIVED ]; public function __construct( public readonly int $policyId, public readonly int $versionNumber, public readonly ?string $body = null, public readonly string $status = self::STATUS_DRAFT, public readonly ?string $publishedAt = null, public readonly ?int $id = null, ) {} public static function fromRow( \stdClass $row ): self { return new self( policyId: Val::int( $row->policy_id ), versionNumber: Val::int( $row->version_number ), body: Val::stringOrNull( $row->body ), status: Val::string( $row->status ), publishedAt: Val::stringOrNull( $row->published_at ), id: Val::int( $row->id ), ); } public function isPublished(): bool { return self::STATUS_PUBLISHED === $this->status; } /** * The body as display-ready HTML. * * Policy bodies are typed into a plain textarea, so most are written as * blank-line-separated prose with no markup at all — dropped into a page * as-is that collapses into one unreadable run of text. Running the same * `wpautop()` WordPress applies to post content turns those breaks into * paragraphs, and leaves bodies that do carry markup alone. */ public function bodyHtml(): string { return wpautop( wp_kses_post( (string) $this->body ) ); } /** * Returns a plain array representation of the version. * * @return array */ public function toArray(): array { return [ 'id' => $this->id, 'policy_id' => $this->policyId, 'version_number' => $this->versionNumber, 'body' => $this->body, 'status' => $this->status, 'published_at' => $this->publishedAt, ]; } }