Problem
#[Publishable] (with publishedAt) exists for components: a component can be in draft state even inside a published page. There is no equivalent for pages themselves.
Currently, the only "draft" signal for a page is the absence of a Route. This works before publication, but once a page is live (route exists), there is no way to take it offline without deleting the route — losing URL history and any redirects pointed at it.
Desired behaviour
AbstractPage (base of Page and AbstractPageData) gets a publishedAt: ?\DateTimeInterface column via #[Publishable] (or a new #[PublishablePage] attribute if there are conflicts with the existing component-level implementation)
- Unpublished pages (
publishedAt: null) are invisible to unauthenticated users via the existing voter infrastructure (AbstractRoutableVoter, RouteVoter)
- Admins can still access and edit unpublished pages via the entity IRI (same as draft components today)
RouteVoter gates public route resolution by the target page's publishedAt state
- The
resource_manifest endpoint respects the same gate
Things to consider
#[Publishable] today lives on components; its voter logic (PublishableVoter?) would need to be checked / extended for page-level entities
Page.isTemplate pages should probably always be accessible to admins regardless of publishedAt (template pages are never public-facing)
- Draft page data entities with an existing Route — the route should resolve to 404/403 for public traffic, not 500
- Interaction with
cascadeChildPaths and the children endpoint: should unpublished children be hidden from the public children list?
- Migration: existing pages have no
publishedAt — default to null (draft) or to now() (treat existing pages as already published)? Likely now() for backwards compatibility
Acceptance criteria
Problem
#[Publishable](withpublishedAt) exists for components: a component can be in draft state even inside a published page. There is no equivalent for pages themselves.Currently, the only "draft" signal for a page is the absence of a Route. This works before publication, but once a page is live (route exists), there is no way to take it offline without deleting the route — losing URL history and any redirects pointed at it.
Desired behaviour
AbstractPage(base ofPageandAbstractPageData) gets apublishedAt: ?\DateTimeInterfacecolumn via#[Publishable](or a new#[PublishablePage]attribute if there are conflicts with the existing component-level implementation)publishedAt: null) are invisible to unauthenticated users via the existing voter infrastructure (AbstractRoutableVoter,RouteVoter)RouteVotergates public route resolution by the target page'spublishedAtstateresource_manifestendpoint respects the same gateThings to consider
#[Publishable]today lives on components; its voter logic (PublishableVoter?) would need to be checked / extended for page-level entitiesPage.isTemplatepages should probably always be accessible to admins regardless ofpublishedAt(template pages are never public-facing)cascadeChildPathsand the children endpoint: should unpublished children be hidden from the public children list?publishedAt— default tonull(draft) or tonow()(treat existing pages as already published)? Likelynow()for backwards compatibilityAcceptance criteria
AbstractPageand/orAbstractPageDatagain apublishedAtcolumnGET /_/routes/{path}to a page withpublishedAt: nullreturns 403/404GET /_/resource_manifest/{uuid}for an unpublished page works forROLE_ADMIN