goldnead/statamic-products
goldnead/statamic-products is a Statamic addon for building and managing product listings within your site. It provides product data structures and publishing tools to help you create organized, searchable catalogs in Laravel/Statamic projects.
composer update to ^1.12.0. This is a minor release: no breaking changes, no deprecations, no migration step. It builds on 1.11.1, so a single upgrade covers both the file-picker boundary fix and the new credit-line timestamps.AccessContainers::allow([...]).Access::creditLines(...), Access::creditLinesAt($at), and AccessView::creditLines(includeEnded: …, at: $at).use Access;
// Lines that existed at the moment the grant was issued.
$linesAtGrant = Access::creditLinesAt($grant->granted_at);
// Current behaviour is untouched:
$linesNow = Access::creditLines();
downloads/baraye/) — the 1.10.x regression symptom; (2) create a new credit line, then call creditLinesAt() with a timestamp before that line was added and confirm it is excluded.Replay grants with at, don't re-derive history by hand. The point of added_at is that a line added to an already-granted access neither appears as "missing" to earlier purchases nor gets re-issued to them. Anywhere you currently compute "what was included", pass the grant time:
use AccessView;
$included = AccessView::creditLines(
includeEnded: false,
at: $grant->granted_at,
);
Keep the "current" and "as-of" paths visibly separate. creditLines() without at behaves exactly as before, so leaving existing call sites alone is safe. Introduce a small helper or query object so new code has to make a deliberate choice, rather than sprinkling at: arguments through controllers:
final class GrantSnapshot
{
public static function lines(Grant $grant): Collection
{
return AccessView::creditLines(
includeEnded: false,
at: $grant->granted_at,
);
}
}
Let added_at be set by the model, never by the caller. The model stamps it when it numbers the line. A form, importer, or API payload that carries added_at will be ignored on the way back out (the stored value wins), so don't build UI fields for it and don't write to it from imports.
Backfill deliberately, if at all. Lines from the takeover (which bring their own number) and anything saved before 1.12.0 have no added_at, meaning "has always been there". Decide a policy per environment: either accept that pre-upgrade lines are treated as infinitely old, or backfill once via a direct write (a normal save will not let you change it) and record the cutoff date in your release notes.
Point the picker at the parent, not the leaf. Subfolders are visible again since 1.11.1, so scope to clients/42 and let users drill into clients/42/invoices. Before that patch you had to guess the exact leaf folder, which made dynamic folder structures nearly unusable.
Treat the server boundary as the contract. The container is enforced server-side on every file operation and rejects anything outside the folder, including ../ traversal. The container is a path boundary, not an authorization system — keep your Gate alongside it:
Gate::define('use-file-picker', fn (User $u, Client $c) => $u->can('view', $c));
$this->authorize('use-file-picker', $client);
AccessContainers::allow(['container' => "clients/{$client->id}"]);
Wire rejections back into the UI. The picker no longer prevents navigation out of the container, so a user can browse to a sibling folder and try to select from it. Catch the rejection around the store/attach call and surface "That file is outside your folder" instead of a raw 403 page.
Testing. Two suites now:
clients/1/downloads/baraye/file.pdf, point the container at clients/1/downloads, assert the subfolder lists and a selection inside it succeeds; then assert clients/2/... and ../../../etc/passwd are rejected.Carbon::setTestNow(), create a second, and assert creditLinesAt() around both boundaries — including the exact instant ($at === $added_at) so the inclusive/exclusive edge is pinned by a test, not by assumption. Also assert a legacy row with added_at = null is returned for very old timestamps.null added_at means "always existed", not "never existed". Legacy and takeover lines are included in every creditLinesAt() query. If you need historically accurate sums, that's a conscious policy decision, not something the package decides for you.added_at is immutable once set. A form or import that drops or changes it gets the stored value back. Correcting a bad value requires a direct DB update; don't plan on fixing it through the model.$at through Carbon rather than string-comparing. Pin the boundary (line added exactly at $at) with a test in your app; don't infer it from a report.ended_at is unchanged. at: filters when a line started existing; includeEnded still governs whether ended lines are returned. Combine them explicitly rather than assuming at: also rewinds expiries.../ traversal, unchanged since 1.10.0) is the guarantee to lean on.composer.lock pinned to 1.10.x reproduces the old bug exactly.at in them. Any cached balances or line lists computed with creditLinesAt() must include the timestamp (and includeEnded) in the key. Same class of bug as a stale container path: a cached "as-of" result silently serves the wrong moment.GrantSnapshot-style class. When the next boundary- or history-related patch lands, you'll have a single place to test and adjust instead of a scattering of inline allow() and at: calls.How can I help you explore Laravel packages today?