Repository navigation
Releases: goldnead/statamic-events
Release list
2.9.0
Changed
- Settings: the addon now declares
settingsNavParent()([section, name]of its own Control Panel navigation entry). With statamic-brand-context 1.16 or newer its settings show as the sub-item "Einstellungen" under that entry; addons without a navigation of their own share one "Addon-Suite" page under Settings. Older brand-context versions ignore the method and keep the previous layout.
Upgrading
- No migration, no new config key.
2.8.0
Added: RSVP and a programme per event
events.rsvp.enabled, off by default; nothing changes for installs that leave it off.event_rsvpstable (unique per event and account),Rsvpsservice (toggle,set,remove,
list,counts,statusFor,eraseFor),RsvpPolicy, and{{ events:rsvp event="..." }}.
Only signed-in accounts answer; onlymanage eventssees names; everyone else gets numbers.events.program(JSON), a time and title grid in the event form.- No accounts contributor: this addon does not know
statamic-accounts, socomposer.jsonsuggests
it andRsvps::eraseFor()is the call to make on deletion.
2.7.0
Added: a date can say when its tickets go on sale
presale_starts_at, nullable and UTC on disk like starts_at. A sold date has two instants that
matter — when the room opens and when the box office does — and the second is what a presale mailing
is timed against; "Der Vorverkauf hat gestartet" is only true from this moment on. Nullable because
a date without a presale date is the normal case, not an error: a placeholder like now() would
fire a mailing on a day nobody chose.
In the Control Panel the field sits in the Tickets section of the date form, shown in the event's
timezone like the two dates above. A presale after the date itself is refused, as a field error on
the form and as a model invariant — the controller check is the friendly path, not the authority.
Setting, moving or clearing the date fires OccurrencePresaleChanged, which carries
previousPresaleStartsAt because "the presale begins later now" is only worth sending with the old
day attached. Creation fires nothing new: a new date announces itself through OccurrenceScheduled,
presale date included, and a second event for the same row would let a listener fire twice on one
write. statamic-marketing can listen from here.
2.6.3
2.6.0
Added: a date can say where it is on a map, and how to get in
venue_city answers "where is it" for a reader. It does not answer "is this within 50 km of me",
which is the question a regional invitation asks before it is sent and the one a map asks before it
draws a pin. Occurrences now carry venue_postal_code, latitude and longitude, indexed as
(brand_id, latitude, longitude) so a bounding box can narrow before anything is measured. The
postal code is a string: Austria writes A-1070, the Netherlands 1011 AB, and every five-digit
assumption breaks on the first concert across the border. The radius query itself is not here — it
belongs to whoever asks the distance question.
Ticketing rides along, because it is the same question about the same date. tickets_url and
is_free sit on the occurrence rather than the event: a tour sells each night separately, and an
event-level link would send everybody to the wrong night. is_free exists so that a missing link
reads as "free" instead of as "we forgot the link".
Fixed: the index-width guard no longer calls a DECIMAL unindexable
tests/Unit/IndexKeyLengthTest.php measured every column type it knew and reported the rest as
wider than InnoDB allows, which is right for text and json and wrong for decimal. A
decimal(10,7) occupies six bytes, not 3073, and the guard rejected the coordinate index at 6154
bytes against a 3072 limit. It now packs DECIMAL the way MySQL does, four bytes per nine digits
plus a tail on each side of the point, and knows float and double too.
2.5.0
Changed: a new event starts in the timezone of the person filling the form in
The timezone field was prefilled from events.defaults.timezone and then from app.timezone, which
on most installs means UTC — so an editor in Frankfurt typed 19:00 and got 19:00 UTC unless they
remembered to change a field they had no reason to look at.
Four sources now, nearest first:
- The logged-in user's, if the install gave its user blueprint a
timezonefield. Statamic
carries no per-user timezone of its own — there is no property for it and no preference in the
core — so this is an invitation rather than an assumption: an install that adds the field is
read, one that does not notices nothing. - The addon's own
events.defaults.timezone, because somebody set it for events specifically. statamic.system.display_timezone, the zone the site already speaks about times in.app.timezone, else UTC.
The user's value is checked rather than believed — a free text field eventually contains "MEZ", and
an invalid zone in a required field is a form that will not submit without saying why. Prefill only:
the field stays editable and existing events are untouched.
The person is read where a person fills a form in, and nowhere else. The creating hook on the
model fires on every path that makes a row — a seeder, an import, a queue job, a command that happens
to run while somebody is signed in — and it falls back to the configured chain alone. Had it read the
current user there, every imported row would be stamped with whoever was logged in at the time, and
two identical import runs by two admins would produce different results, silently, showing up only
as a start time that reads wrong. Those are two different questions: what a form opens with may know
who is looking, what gets stored when nobody said anything may not.
Documented: the event types were already configurable
config('events.types') has driven the list since the type field was built, with the six shipped
values as its default and a host free to replace them; a type removed from the config keeps being
offered on an event that still carries it, because the stored value is a free string. That was true
and untested, which is the same thing as unpromised — there is now a test for both halves.
2.4.0
Changed: the events listing shows an empty state instead of HTTP 500 when its tables are missing
The addon can be installed without its migrations having run — composer pulls the package in, the
nav item appears, and events and event_occurrences still do not exist. The listing asks both
tables a question while the page is being built (it counts each event's dates with
withCount('occurrences')), so the visitor got HTTP 500 and a stack trace for what is really an
unfinished setup. The listing now checks before its first query and renders a setup screen that
names the missing tables and says to run php artisan migrate.
The reason does not vanish with the 500: the guarded page writes to the log why it turned somebody
away. Otherwise the site would look installed and never work.
2.3.0
Changed: the event detail screen is the form
The screen used to be read-only. Type, status, visibility, timezone, slug and description sat in a
grey card as key/value pills, and an "Edit" button led to a second screen carrying the same
blueprint. A collection entry has no such break: what is on the screen is the field, and Save sits
top right. This screen now works the same way — same blueprint, same PATCH /cp/events/{event},
core's own PublishContainer and tabs, with the sidebar coming from the blueprint's sidebar tab.
GET /cp/events/{event}/edit is gone with it, and so is the "Edit" row action on the events
listing: it would have been the same link as "View" under a second name. Anything linking to the
edit URL should link to the event's own screen.
The calendar feed URL keeps a panel of its own below the dates. It is not a field of the event — it
belongs to the installation — and it is the only place in the Control Panel that shows it.
Changed: dates are edited in a stack, not on a page of their own
"Add date" and "Edit" on a single date opened a separate screen and navigated away
from the event. Both now open a stack over the event. The two routes are unchanged and still answer
an ordinary request; the stack asks them for JSON, which is what Statamic\CP\PublishForm hands its
own Vue page. Saving goes through core's save pipeline, so the error toast, the 422 field errors and
the dirty-state guard are core's rather than rebuilt here.
Added: a settings screen under Control Panel → Addon Settings
Eight keys are editable per brand without a deploy: the timezone and visibility a new event starts
with, the five calendar feed keys, and the listing page size. Everything not changed keeps following
config/events.php, so an upgrade still moves the defaults.
Screen, form, validation, store and permission check come from
goldnead/statamic-brand-context (now required at ^1.13); this package contributes the field list
in Goldnead\Events\Support\Settings and one new permission, manage events settings. The two
existing permissions are untouched.
The new permission is held by nobody until it is given to a role: until then the section is
invisible, including to users who may do everything else with this addon.
The ^1.13 floor is not cosmetic. Older releases carry the screen but do not apply its values
reliably. On a single-brand install the settings of the addons that registered last were not
applied at all — the screen showed the stored value after a reload while config() kept answering
with the packaged one, and the brand switch that would have caught up never happens where there is
one brand. And up to 1.12 a second save of the same section deleted the override made by the first,
without a message. Anyone who set values between 06.09. and this update should check the screen
afterwards: lost values do not come back on their own.
Three keys are deliberately not on the screen, and the group descriptions say so: cp.enabled and
bridges.activity are read while the application boots, so a switch here would only take effect on
the next deploy, and types is a handle-to-label map that no settings field type can edit honestly.
2.2.0
Fixed: the header actions were glued together
ButtonGroup is a segmented control in Statamic — one border around everything, dividers instead
of gaps. Right for the list/grid switch, wrong for a header's actions: "All events" had no button
of its own and the primary button sat flush against the "…" menu. The actions are siblings in the
header slot now, the way Events/Index.vue and core's own headers do it.
Fixed: two tests asserted an order the query never promised
The status breakdown is sorted by count, descending, and says nothing about rows that tie. SQLite
and MySQL break a tie differently, so the suite was green locally and red on CI. The tests now
check what is actually guaranteed — the figures, and that they never rise — which is more than
they checked before. A deterministic tie-break belongs in TableMetric::splitByColumn of
statamic-insights, which thirteen addons carry byte for byte; that is its own ticket.
Pint also tripped over tests/Fakes/insights-contracts.php. All three pinned copies are excluded
now, not just one.
Changed: an event's dates are a Statamic table now
The dates on an event's screen were a stack of blocks with the actions written out as text links
underneath each one. Five dates filled the panel, nothing could be sorted, nothing could be picked,
and it did not look like any other screen in the Control Panel. They are core's <Listing> now, in
its client-side mode: the dates arrive complete as an Inertia prop, so there is no new route and
nothing to page through.
Four columns — the window, the timezone, the location and the status — with sortable headers, a
checkbox column and a … menu per row. Nothing that was on screen before has gone: the all-day
badge sits beside the window, the timezone and the location are columns of their own, and a
cancelled date says so in the status column instead of a red pill in the corner.
The window no longer repeats the date on the end of a same-day event. Tue, 15 Sep 2026 19:00 – 21:30, not the same date twice, which in a column costs exactly the width the location needs.
Changed: cancelling and deleting a date are registered actions
They were two buttons per row, each with a hand-built confirmation modal, and they only ever worked
on one date. They are Statamic\Actions\Action classes now, which is what earns the listing its
checkbox column: the same action runs from a single row's … menu and from the bulk bar over a
whole selection, through POST cp/events/occurrences/actions, with core's confirmation, core's
toast and core's authorization.
Neither action declares a redirect(), and that is deliberate. A redirect looks like the obvious way
to get a client-side listing to show the new state, and it silently costs the success message: core's
ActionController::run() returns from the redirect branch before it reads what the action returned,
so the front end falls back to toasting its own "Action completed". The screen refreshes through the
listing's refreshing event instead, which is what Events/Index.vue already does.
Removed with them: POST cp/events/occurrences/{occurrence}/cancel and
DELETE cp/events/occurrences/{occurrence}, along with OccurrenceController::cancel() and
::destroy(). They existed to serve the buttons and had no caller left. Control Panel routes are
not part of this package's public API (see the note at the top); the Antlers tags, the config keys
and the facade are untouched.
An id a selection carries that the brand scope does not reach is now a 404 rather than a shrunken
selection. Core decides which actions apply by comparing counts, so an empty collection satisfies
every action registered in the Control Panel — verified in the playground, where one foreign-brand
id came back with core's asset actions and a 500.
Fixed: the event screen no longer borrows another addon's stylesheet
The two panels sat side by side through a pair of responsive grid classes that Statamic core does
not emit. They came from statamic-marketing and statamic-clientrooms, so the layout only ever
appeared on an installation that happened to carry one of them; on its own this addon fell back to a
stacked page.
They stack on purpose now, and the reason is the table rather than the missing classes — side by
side is buildable with classes core does emit. Measured: the table needs 988px, two thirds of a
detail screen gives it 739, and the … column then sits 242px outside the visible area, reachable
only by scrolling the table sideways. Stacked it gets 1128.
The location column keeps one line per row, the way core's listings do. Letting an address wrap put
every row at 125px on a 390px screen where core's collections listing sits at 49-65 — and the table
scrolled sideways anyway. Rows are 47px now at both widths.
2.1.1
Fixed: delete moved out of the body and into the page header
The delete button sat in the middle of the page body under the calendar feed: hard to find and
easy to hit by accident. It is now a DropdownItem variant="destructive" in the header's …
menu, which is where core puts a destructive page action.
Three icon names that do not exist: book-open-cover, plus globe and map-pin inside a bound
expression, where no literal grep finds them. An unknown name renders an empty box and warns
about nothing.