Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,33 @@ ICS downloads read the following entry fields when present:

| ICS property | Source |
|---|---|
| `LOCATION` | `address`, falling back to `location` — only when the value is a string |
| `URL` | `online_url`, then deprecated `link`, then deprecated `location` when that string is a URL |
| `GEO` | `coordinates` (`latitude` / `longitude`) |
| `LOCATION` | `location.name`, falling back to `online_url` when the name is empty |
| `URL` | `online_url` |
| `GEO` | `location.coordinates` (`latitude` / `longitude`) |
| `DESCRIPTION` | `description` |

A URL-valued `location` (with no separate `address` / `online_url` / `link`) currently emits **both** `LOCATION:` and `URL:`. Prefer `online_url` for join links; `link` and the URL-valued `location` fallback are deprecated and will be removed in 7.0.
`location` and `online_url` are independent and combinable (hybrid events).

The `coordinates` field must be a keyed array:
| Event | `LOCATION:` | `URL:` | `GEO:` |
|---|---|---|---|
| Physical only | `location.name` | — | `location.coordinates` |
| Online only | `online_url` | `online_url` | — |
| Hybrid | `location.name` | `online_url` | `location.coordinates` |

`location` must be a group. A string or other non-group value is skipped (no `LOCATION:` from it). Nested coordinates shape:

```php
'coordinates' => [
'latitude' => 40,
'longitude' => 50,
'location' => [
'name' => '123 Main St',
'coordinates' => [
'latitude' => 40,
'longitude' => 50,
],
],
```

Partial or non-numeric coordinates are skipped (no `GEO:`) rather than failing the download.

If your field names differ from the defaults above, use a [Computed Value](https://statamic.dev/content-modeling/computed-values#defining-computed-values) to map them.

---
Expand All @@ -83,9 +94,19 @@ Using the sample fieldset is the fastest way to get started.

| Field | Description |
|-------|-------------|
| `online_url` | Join link for online or hybrid events (Zoom, livestream, etc.). Optional; can be combined with a physical place once `location` is declared. |
| `location` | Group: `name` (localizable text — venue, description, or address) and optional nested `coordinates` (`latitude` / `longitude`). |
| `online_url` | Join link for online or hybrid events (Zoom, livestream, etc.). Optional; independent of `location`. |

### Upgrading to 7.0

Breaking changes for location fields:

- `location` is now a **group** (`name` + nested `coordinates`), not a string
- `address`, `link`, and top-level `coordinates` are no longer read
- URL sniffing on a string `location` is gone — use `online_url` for join links
- Protected `eventUrl()` / `icsAddress()` were replaced by `icsUrl()` / `icsLocation()`

`link` is deprecated in favour of `online_url` and will be removed in 7.0.
An update script (#196) migrates common legacy handles. Back up content before upgrading. Computed-value mappings and foreign (e.g. Prime) `location` shapes need a separate cutover.

### Single-Day Events

Expand Down
38 changes: 38 additions & 0 deletions resources/fieldsets/event.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,44 @@ fields:
field:
type: section
display: Location
-
handle: location
field:
type: group
display: Location
fullscreen: false
border: false
localizable: true
instructions: 'Physical place for the event — a plain description or an address. Optional; combine with Online URL for hybrid events.'
fields:
-
handle: name
field:
type: text
display: Name
localizable: true
instructions: 'Venue name, plain description, or street address (ICS LOCATION).'
-
handle: coordinates
field:
type: group
display: Coordinates
fullscreen: false
border: false
instructions: 'Optional map coordinates for ICS GEO.'
fields:
-
handle: latitude
field:
type: float
display: Latitude
width: 50
-
handle: longitude
field:
type: float
display: Longitude
width: 50
-
handle: online_url
field:
Expand Down
49 changes: 19 additions & 30 deletions src/Types/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use DateTimeInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use RRule\RRuleInterface;
use Spatie\IcalendarGenerator\Components\Event as ICalendarEvent;
use Statamic\Entries\Entry;
Expand Down Expand Up @@ -133,59 +132,49 @@ protected function buildICalendarEvent(string|CarbonInterface $date): ICalendarE

protected function decorate(ICalendarEvent $iCalEvent): ICalendarEvent
{
if ($address = $this->icsAddress()) {
$iCalEvent->address($address);
if ($location = $this->icsLocation()) {
$iCalEvent->address($location);
}

if ($this->hasValidCoordinates($coords = $this->event->get('coordinates'))) {
if ($this->hasValidCoordinates($coords = Arr::get($this->event->get('location'), 'coordinates'))) {
$iCalEvent->coordinates((float) $coords['latitude'], (float) $coords['longitude']);
}

if (! is_null($description = $this->event->description)) {
$iCalEvent->description($description);
}

if (! is_null($url = $this->eventUrl())) {
if (! is_null($url = $this->icsUrl())) {
$iCalEvent->url($url);
}

return $iCalEvent;
}

protected function eventUrl(): ?string
// Entry::get() is untyped; keep mixed so a bad value can't TypeError the public ICS route.
protected function hasValidCoordinates(mixed $coords): bool
{
if (is_string($url = $this->event->get('online_url')) && $url !== '') {
return $url;
}

// @deprecated Will be removed in 7.0. Use online_url.
if (is_string($link = $this->event->get('link')) && $link !== '') {
return $link;
}
return is_array($coords)
&& is_numeric($coords['latitude'] ?? null)
&& is_numeric($coords['longitude'] ?? null);
}

// @deprecated Will be removed in 7.0. Use online_url.
$location = $this->event->get('location');
protected function icsLocation(): ?string
{
$name = Arr::get($this->event->get('location'), 'name');

if (! is_string($location) || $location === '') {
return null;
if (is_string($name) && $name !== '') {
return $name;
}

return Str::isUrl($location) ? $location : null;
return $this->icsUrl();
}

protected function icsAddress(): ?string
protected function icsUrl(): ?string
{
$address = $this->event->address ?? $this->event->get('location');

return is_string($address) && $address !== '' ? $address : null;
}
$url = $this->event->get('online_url');

// Entry::get() is untyped; keep mixed so a bad value can't TypeError the public ICS route.
protected function hasValidCoordinates(mixed $coords): bool
{
return is_array($coords)
&& is_numeric($coords['latitude'] ?? null)
&& is_numeric($coords['longitude'] ?? null);
return is_string($url) && $url !== '' ? $url : null;
}

protected function supplement(CarbonInterface $date): ?Entry
Expand Down
Loading
Loading