Repository navigation
2.5.0 — SilverStripe 6 line
Fixed
-
(#174, closing the known gap 1.9.0 left open)
pages/$ID/apply-templatewith
{"publish": "recursive"}now publishes each element's own duplicated descendants, not just the
elemental area and its top-level elements.1.9.0 flagged this as unverified. The mechanism is now confirmed:
TemplateElementDuplicator::duplicateElements()duplicates each template element with a bare
$element->duplicate(), soDataObject::duplicate()decides what children get created. An
element's children are only reachable by the publish walk if that element declares$owns
itself —BaseElementdoesn't — so anythingduplicate()created that$ownsdidn't name
stayed on draft after a"recursive"apply, unpublished and unauthorized, with no error and no
signal.Scope, stated precisely: no stock Dynamic element actually hits this today. The elements
with versioned children list them in$ownsas well (ElementPhotoGallery=>Images,
ElementCard=>ElementLink), so the existing walk already reached them; the one genuine
cascade-only case,ElementStatCounters=>Stats, has an unversioned child with nothing to
publish. The gap is structural rather than currently-triggered — nothing stops an element
declaring$cascade_duplicateswithout a matching$ownsfor a versioned child, and that
combination silently loses content.PageHandler::applyTemplate()now walks each element via the new
OwnedTreeWalker::walkDuplicates()and passes the result topublishOwnedTree()alongside the
area and elements. That walk reproduces whatduplicate()creates rather than reading
$cascade_duplicatesdirectly, which takes two corrections in opposite directions:- An empty
$cascade_duplicateson aVersionedrecord makes the framework substitute
$owns ∩ (many_many + belongs_to + has_many)(RecursivePublishable::onBeforeDuplicate()).
Without this, grandchildren went missing at depth 2+, where the page-level$ownswalk can no
longer cover for it. - A many_many entry creates nothing —
duplicateManyManyRelation()link-copies, attaching
the same pre-existing records to the clone. Publishing those would push shared, possibly
deliberately-draft records live and would newly403on classes no project allowlists.
OwnedTreeWalker's cycle guard, depth cap, diamond handling, and unversioned-record handling are
unchanged and now serve both walks.walk()'s signature is unchanged.Behavior change, worth reading before upgrading: a class reached only through the new walk
that grantscreate/updatebut withholdsactionnow gets403 FORBIDDEN_CLASSon a
"recursive"apply-template, and the whole call rolls back — the same behavior 1.9.0 introduced
for the composition path, now reaching these records too.Two things widen that further than it first looks, both deliberate:
- It applies to children of every element on the page, not only the ones the template just
added. The endpoint has always published every element on the page; that breadth now extends
to their children. - The
$ownsfallback reaches versioned records sitting below an unversioned intermediate,
which nothing previously walked to at all.
So an
apply-templatecall that succeeded before can now hard-fail on a class the project never
had to allowlist. That is the correct direction — those records really were created by the
duplicate and really do need publishing, and failing loudly beats leaving them silently on draft
— but it is a live endpoint changing behavior. Fix: grantactionon the affected classes, or
pass"publish": "none"if the cascade was never meant to publish them.This is also the first test coverage of any kind for
apply-template's publish behavior.
dynamic/silverstripe-elemental-templatesremainssuggested rather than required; the tests
drive stubs via two newPageHandlerconfig values,template_classand
template_applicator_class, which default to the real package's class names. - An empty