refactor(entry-actions): dedup scope guard + guard publish cache-flush

Address code-review findings on the trip publish/unpublish toggle:

- Extract EntryScopeGuard::resolveChildOf() so resolveActiveDailyChild and
  resolveTripChild share one find() + parent-route-assert body instead of two
  copies that could drift (P1 maintainability).
- Wrap setTripPublished's post-save cache invalidation in try/catch. save() has
  already persisted the published flag to disk, so a flush failure now logs a
  loud reconciliation warning (and still returns success + the audit line)
  rather than bubbling to a bare 500 that reads as "nothing happened"
  (P2 reliability / adversarial).

Behavior-preserving; PHP lint clean; trip-publish Playwright suite 8/8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
2026-07-08 17:06:17 +02:00
co-authored by Claude Opus 4.8
parent 064f0f0c52
commit 5b4e31678e
2 changed files with 42 additions and 33 deletions
@@ -97,19 +97,17 @@ class EntryScopeGuard
}
/**
* Resolve a folder segment to the page that is a DIRECT child of the active
* trip's dailies container, or null when the segment is unsafe, no active trip
* is set, the page does not exist, or its parent is not the active dailies.
* Resolve a safe segment to the page that is a DIRECT child of $parentRoute,
* or null when the segment is unsafe, the page does not exist, or its parent
* is not exactly $parentRoute. Resolving via $pages->find() + a parent-route
* assertion (never raw path concatenation) is what closes the traversal hole;
* both public resolvers below share this one body so they cannot drift.
*/
public static function resolveActiveDailyChild(Grav $grav, string $segment): ?PageInterface
private static function resolveChildOf(Grav $grav, string $parentRoute, string $segment): ?PageInterface
{
if (!self::isSafeSegment($segment)) {
return null;
}
$dailies = self::dailiesRoute($grav);
if ($dailies === null) {
return null;
}
$pages = $grav['pages'];
// In the API request context the page tree is lazily disabled; enable it
// so find() can resolve (mirrors the api plugin's own resolvePageByRoute).
@@ -117,17 +115,31 @@ class EntryScopeGuard
if (method_exists($pages, 'enablePages')) {
$pages->enablePages();
}
$page = $pages->find($dailies . '/' . $segment);
$page = $pages->find($parentRoute . '/' . $segment);
if ($page === null) {
return null;
}
$parent = $page->parent();
if ($parent === null || $parent->route() !== $dailies) {
if ($parent === null || $parent->route() !== $parentRoute) {
return null;
}
return $page;
}
/**
* Resolve a folder segment to the page that is a DIRECT child of the active
* trip's dailies container, or null when the segment is unsafe, no active trip
* is set, the page does not exist, or its parent is not the active dailies.
*/
public static function resolveActiveDailyChild(Grav $grav, string $segment): ?PageInterface
{
$dailies = self::dailiesRoute($grav);
if ($dailies === null) {
return null;
}
return self::resolveChildOf($grav, $dailies, $segment);
}
/**
* Resolve a slug to the trip page that is a DIRECT child of /trips, or null
* when the segment is unsafe, the page does not exist, or its parent is not
@@ -140,23 +152,6 @@ class EntryScopeGuard
*/
public static function resolveTripChild(Grav $grav, string $slug): ?PageInterface
{
if (!self::isSafeSegment($slug)) {
return null;
}
$pages = $grav['pages'];
// In the API request context the page tree is lazily disabled; enable it
// so find() can resolve (mirrors resolveActiveDailyChild). Idempotent.
if (method_exists($pages, 'enablePages')) {
$pages->enablePages();
}
$page = $pages->find('/trips/' . $slug);
if ($page === null) {
return null;
}
$parent = $page->parent();
if ($parent === null || $parent->route() !== '/trips') {
return null;
}
return $page;
return self::resolveChildOf($grav, '/trips', $slug);
}
}