Calendar & Events — CalendarService + Designer
The Calendar is a standalone event-management app (like Google Calendar) that PPPHP can add to any generated application. It provisions its own tables, lets users create and share events, schedule meetings, invite people and groups, set reminders, and import/export iCal — all without a business table. This guide covers how to enable it, customize it in the Designer, and call its PHP API from your own code.
Contents
- Enabling the Calendar
- Customizing in the Designer
- Region reference (Header / Toolbar / Sidebar / Grid / Footer)
- Tables & the visibility model
- Server API —
CalendarService - Reminders & notifications
- iCal import / export & recurrence
- The generated JSON endpoint
- Front-end —
PPCalendarApp.mount() - Recipes
Enabling the Calendar
Open the Tables module and click Create Calendar. Toggle
Enable the Calendar app in this project and pick your defaults (default view, first day of
week, menu label, personal calendars, in-app notifications, email reminders), then
Save & enable. On the next Build, the calendar is generated at
generated/calendar/index.php and a menu link is added.
The enable switch lives only in this modal — it is what turns the calendar on for the project. Everything about how it looks and behaves is edited in the Designer (below), which overrides these defaults. The calendar creates its tables automatically the first time a signed-in user opens it.
Customizing in the Designer
Open Designer → Common Pages → Calendar. What you see on the canvas is exactly what the build generates. The calendar is one smart component whose regions are individually clickable: click the Header, Toolbar, Sidebar, Grid or Footer and the Properties panel focuses on that region. Click the calendar background (or use the component chip) to edit all settings at once.
Every control here provably changes the generated app — the Designer only exposes settings the live calendar can honour, so what you design is what you get. To start over, use Reset to default on the page; it restores the standard template.
Region reference
| Region | Settings |
|---|---|
| Header | Heading, subtitle, menu label, alignment, accent colour, and heading typography (font, size, colour). |
| Toolbar | Default view; show/hide each view (Week, Day, Agenda, Year — Month is always on); Today button; prev/next arrows; Import/Export buttons; the “New event” button and its label. |
| Sidebar | Show/hide the whole side panel, and per-section toggles: Create button, mini-month navigator, Calendars list, Categories, Upcoming events. |
| Grid | Week-start day, weekend shading, Today colour, grid-line colour. |
| Footer | Show an optional caption line under the calendar, with custom text. |
| Appearance | Base font for the whole calendar app. |
| Notifications | Personal calendars, in-app notifications, email reminders. |
Fonts & colours. Font pickers use the app’s bundled web fonts (add more via the local
fonts pipeline). Colour and size fields accept CSS values (e.g. #ef4444, 1.8rem);
they are sanitised at build time before being written into the page.
Tables & the visibility model
The calendar self-provisions five pp_ tables (created on first run, healed on upgrade):
pp_calendars— a calendar owns events; each user gets a personal “My Calendar”.pp_events— title, description, location, start/end, all-day, colour, icon, image, visibility, recurrence.pp_event_shares— share an event or calendar with everyone, a group, or a user (view / edit).pp_event_attendees— invitees and their RSVP (pending / accepted / declined / tentative).pp_event_reminders— minutes-before + method (popup / in-app / email).
Who can see an event? A user sees an event if they own it, OR it is public, OR it is shared to them (directly, via a group, or to everyone), OR they are an attendee — and admins see all. Editing requires ownership, an edit-share, or admin.
Server API — CalendarService
All logic lives in core/Calendar/ (shipped into every build), so you can call it from your
own event handlers, cron tasks or custom pages. Resolve the current user’s context first:
use PpPhp\Core\Calendar\CalendarSchema;
use PpPhp\Core\Calendar\CalendarService;
CalendarSchema::ensure(); // create/heal tables (safe to call anytime)
$ctx = CalendarService::contextFromApp(); // ['uid' => int, 'groups' => int[], 'isAdmin' => bool]
Reading
// Calendars the user can see (always includes their personal calendar)
$cals = CalendarService::calendars($ctx);
// Events visible to the user in a date window (recurrence expanded to occurrences)
$events = CalendarService::events($ctx, '2026-07-01', '2026-07-31');
// Optionally restrict to specific calendar ids
$events = CalendarService::events($ctx, $from, $to, [1, 4]);
// One event with its shares, attendees and reminders
$ev = CalendarService::getEvent($ctx, $eventId);
Writing
$id = CalendarService::createEvent($ctx, [
'title' => 'Sprint review',
'starts_at' => '2026-07-15 15:00',
'ends_at' => '2026-07-15 16:00',
'location' => 'Room 2',
'color' => '#2563eb',
'visibility' => 'groups', // inherit|private|public|groups|users
'rrule' => 'FREQ=WEEKLY;BYDAY=TU', // optional recurrence
'reminders' => [['minutes' => 15, 'method' => 'notify']],
'shares' => [['principal' => 'group', 'id' => 3, 'permission' => 'view']],
'attendees' => [7, 12],
]);
CalendarService::updateEvent($ctx, $id, ['title' => 'Sprint review (moved)']);
CalendarService::reschedule($ctx, $id, '2026-07-16 15:00', '2026-07-16 16:00');
CalendarService::setRsvp($ctx, $id, 'accepted');
CalendarService::deleteEvent($ctx, $id);
Every write is permission-checked against $ctx. Ids are validated; user text is parameterised.
Reminders & notifications
Events carry reminders with a method: popup (a browser alert, client-side),
notify (the in-app notification bell) or email. Enable the delivery paths in the
Designer’s Notifications region (In-app notifications / Email reminders).
When either is on, the build adds a calendar-reminders cron task that dispatches due reminders:
// Run by the generated cron once a minute; safe to call yourself.
CalendarService::dispatchReminders();
You can also circulate a new event to its audience (attendees + share targets) on creation:
// delivery: 'notify' | 'email' | 'both' | 'none'
CalendarService::circulate($ctx, $eventId, 'both');
In-app notifications are written to the app’s own notifications table (Misc → Advanced → Notifications), so they appear in the same bell your other features use.
iCal import / export & recurrence
use PpPhp\Core\Calendar\CalendarIcs;
// Export visible events to an .ics string
$ics = CalendarIcs::export(CalendarService::events($ctx, $from, $to));
// Parse an uploaded .ics into event arrays (feed to createEvent)
$rows = CalendarIcs::parse($uploadedIcsString);
Recurrence uses standard RRULE (FREQ = DAILY / WEEKLY / MONTHLY / YEARLY, plus
INTERVAL, COUNT, UNTIL, BYDAY). The service stores one
master event and expands occurrences on read via CalendarRecurrence.
The generated JSON endpoint
The built calendar page (generated/calendar/index.php) is both the page shell and a JSON API,
dispatched by ?action=. All writes are POST and CSRF-guarded (token in the JSON body or the
X-CSRF-Token header). Actions:
| Action | Purpose |
|---|---|
bootstrap | Initial payload (calendars, principals, settings). |
events | Events in a from/to window. |
event | One event with shares/attendees/reminders. |
create_event · update_event · delete_event | CRUD. |
reschedule | Drag-move / resize. |
create_calendar | New personal calendar. |
rsvp | Respond to an invitation. |
principals | Users + groups for invite/share pickers. |
import · export | iCal in / out. |
Front-end — PPCalendarApp.mount()
The UI is a single self-contained asset (assets/vendor/ppcal/pp-calendar.js, no CDN). The build
mounts it with the options the Designer produced:
PPCalendarApp.mount(document.getElementById('pp-calendar-app'), {
base: '.../generated/calendar/index.php',
token: '<csrf>',
defaultView: 'month', // month|week|day|agenda|year
firstDay: 0, // 0 = Sunday, 1 = Monday
views: ['month','week','day','agenda','year'], // which view buttons show
showToday: true, showNav: true, showIcs: true,
showNewEvent: true, newEventLabel: 'New event',
weekendShading: false,
showSidebar: true, // + sideCreate / sideMiniMonth / sideCalendars / sideCategories / sideUpcoming
appReminders: true, emailReminders: false, notifEnabled: true,
allowPersonal: true
});
Header typography, accent, Today colour, grid-line colour and base font are applied as scoped CSS / CSS variables on the page — you don’t pass them here; the Designer bakes them into the shell.
Recipes
Seed an event from an order handler:
$ctx = CalendarService::contextFromApp();
CalendarService::createEvent($ctx, [
'title' => 'Deliver order #' . $orderId,
'starts_at' => $dueDate . ' 09:00',
'all_day' => 0,
'color' => '#16a34a',
'reminders' => [['minutes' => 60, 'method' => 'email']],
]);
A read-only, embedded calendar (no sidebar, month only): in the Designer set the Sidebar → Show side panel off, and in the Toolbar turn off every view except Month and hide the “New event” button. Rebuild — the generated page matches exactly.
Brand the calendar: set the Header accent to your brand colour, choose a heading font and size, and (optionally) a base font under Appearance. The mini-month “today”, the primary buttons and event chips all follow the accent.
See also: Notifications (the bell reminders use), Email (email reminders), and the Designer’s built-in Common Pages → Calendar live preview.