=== LiveCal Publisher === Contributors: livecal Tags: calendar, events, track, livecal, gutenberg Requires at least: 6.1 Tested up to: 6.5 Requires PHP: 7.4 Stable tag: 0.2.0 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html Map WordPress posts to LiveCal Sources. Visitors Track your event and add it to their real calendar; you push updates and every tracker's calendar event updates in seconds. == Description == LiveCal is the live layer for calendars — a calendar entry that updates itself. This plugin maps a WordPress post to a LiveCal **Source** via stable post meta, upserts it to the LiveCal Publisher API, and renders a **Track** widget so visitors add the event to their own calendar. The plugin is a thin writer in front of the shipped Publisher API. A WordPress post maps to ONE Source; many trackers' calendar events hang off it. When facts or live-state change, LiveCal PATCHes every tracker's calendar in seconds — the moat versus a static ICS file. = Two writers, one contract = The post-meta keys below are the contract. BOTH the block editor (a human) and a programmatic pipeline (via `update_post_meta()` or the WP REST API) are first-class writers. The sync engine fires on either. * `_livecal_manifest` (string) — manifest slug, e.g. `airshow`. * `_livecal_facts` (object JSON) — confidence-tagged facts: `{ field: { value, confidence: "known"|"provisional" } }`. * `_livecal_state` (object JSON) — `{ status, starts_at, live_state }`. * `_livecal_source_id` (string) — **written back by the plugin** after the first successful sync (read-only to writers). = Editor surfaces = * **Gutenberg block** ("LiveCal Track") — edits the contract meta in the block sidebar and renders the widget on the published page. * **Shortcode** — `[livecal_track source="..."]` (omit `source` to use the current post's Source). * **Classic meta box** — a fallback in the post sidebar for the classic editor. All three render the same element: ``. == Installation == 1. Upload the `livecal-publisher` folder to `/wp-content/plugins/`. 2. Activate **LiveCal Publisher** in the Plugins screen. 3. Go to **Settings → LiveCal Publisher** and paste the per-site API token LiveCal issued you (delivered out-of-band — never committed to code). Optionally paste the **publishable key** (`livecal_pk_…`) to give the rendered widget origin-pinned analytics + live-state enrichment (it is not a secret and is safe in page source), and set the API base URL and which post types sync. 4. Edit a post: set a manifest slug (and starts_at / facts) via the LiveCal Track block or the LiveCal meta box, then Publish. The plugin upserts the Source and writes back `_livecal_source_id`. 5. Add the LiveCal Track block (or `[livecal_track]`) where you want the widget. = Multisite / 120-site networks = Place this `livecal-publisher` folder in `/wp-content/plugins/` once, then copy `livecal-publisher-mu-loader.php` into `/wp-content/mu-plugins/`. Every site in the network then runs the plugin without per-site activation. Each site still holds its OWN token under its own Settings → LiveCal Publisher, so a leak on one site can't touch the network. == Frequently Asked Questions == = Where does the API token go? = Settings → LiveCal Publisher. It is stored in the site's options table, rendered masked, and never written to code or committed. LiveCal delivers it out-of-band. = What happens when a sync fails? = Retryable failures (HTTP 429 / 5xx / network) are retried with exponential backoff via WP-Cron (up to 5 attempts, honoring `Retry-After`). Terminal failures (HTTP 4xx / 422) stop and show an admin notice on the post-edit screen, with the stable error `code` and offending `field`. = Is the upsert idempotent? = Yes. The Source is keyed on `external_id = {site_url}/{post_id}` and upserted via `PUT /v1/sources/{external_id}`, so re-saving a post never creates a duplicate. == Changelog == = 0.2.0 = * Settings: added an optional **Publishable key** field (`livecal_pk_…`). Unlike the API token it is not a secret — it is rendered into the widget markup, where the embed uses it to origin-pin + rate-limit the analytics beacon and to enrich the widget with live-state. The field validates the `livecal_pk_` prefix as a guard against pasting the secret token into a public field. * Render: `` now carries `pk="livecal_pk_…"` when a publishable key is set (site-wide). With no key set, the markup is unchanged — fully keyless. = 0.1.0 = * Initial build: post-meta contract, sync engine (upsert + state push with retry/backoff), Gutenberg block, shortcode, classic meta box, settings page, multisite mu-loader.