← All documentation
Topics

- Working with data
- Designer components
- Field controls
- Search & filtering
- Calendars
- PDF & printing
- Email
- SMS
- Notifications
- Security
- Helper functions
- JavaScript API
- Performance

# 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.