1: <?php
2: declare(strict_types=1);
3:
4: namespace om\ICal\Value;
5:
6: use InvalidArgumentException;
7: use om\ICal\Parameters;
8: use Stringable;
9:
10: /**
11: * CAL-ADDRESS value of ORGANIZER and ATTENDEE (RFC 5545, sections 3.3.3 and 3.8.4).
12: */
13: final readonly class CalAddress implements Stringable {
14: public function __construct(
15: public string $uri,
16: public Parameters $parameters,
17: ) {
18: }
19:
20: /**
21: * An organizer or attendee: CN, ROLE, PARTSTAT, RSVP and CUTYPE parameters from the arguments.
22: * An e-mail address without a scheme becomes a "mailto:" URI.
23: *
24: * CalAddress::create('mailto:a@example.org', name: 'A', role: 'CHAIR', status: 'ACCEPTED', rsvp: true)
25: *
26: * @param ?string $role ROLE, e.g. CHAIR, REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT
27: * @param ?string $status PARTSTAT, e.g. NEEDS-ACTION, ACCEPTED, DECLINED, TENTATIVE, DELEGATED
28: * @param ?string $type CUTYPE, e.g. INDIVIDUAL, GROUP, RESOURCE, ROOM
29: * @param array<string, string|list<string>> $parameters other parameters, e.g. DELEGATED-TO, SENT-BY, LANGUAGE
30: * @throws InvalidArgumentException for an empty URI
31: */
32: public static function create(
33: string $uri,
34: ?string $name = null,
35: ?string $role = null,
36: ?string $status = null,
37: ?bool $rsvp = null,
38: ?string $type = null,
39: array $parameters = [],
40: ): self {
41: $uri = trim($uri);
42: if ($uri === '' || preg_match('/[\x00-\x20\x7F]/', $uri)) {
43: throw new InvalidArgumentException("Invalid calendar user address: $uri");
44: }
45: if (!preg_match('/^[A-Za-z][A-Za-z0-9+.-]*:/', $uri) && str_contains($uri, '@')) {
46: $uri = 'mailto:' . $uri;
47: }
48: $values = array_filter([
49: 'CN' => $name,
50: 'CUTYPE' => $type === null ? null : strtoupper($type),
51: 'ROLE' => $role === null ? null : strtoupper($role),
52: 'PARTSTAT' => $status === null ? null : strtoupper($status),
53: 'RSVP' => $rsvp === null ? null : ($rsvp ? 'TRUE' : 'FALSE'),
54: ], static fn(?string $value): bool => $value !== null);
55: return new self($uri, Parameters::from([...$values, ...$parameters]));
56: }
57:
58: /**
59: * E-mail address of a "mailto:" URI.
60: */
61: public function email(): ?string {
62: return stripos($this->uri, 'mailto:') === 0 ? substr($this->uri, 7) : null;
63: }
64:
65: /** Common name (CN). */
66: public function name(): ?string {
67: return $this->parameters->get('CN');
68: }
69:
70: /** ROLE, default REQ-PARTICIPANT. */
71: public function role(): string {
72: return strtoupper($this->parameters->get('ROLE') ?? 'REQ-PARTICIPANT');
73: }
74:
75: /** Participation status (PARTSTAT), default NEEDS-ACTION. */
76: public function status(): string {
77: return strtoupper($this->parameters->get('PARTSTAT') ?? 'NEEDS-ACTION');
78: }
79:
80: /** Calendar user type (CUTYPE), default INDIVIDUAL. */
81: public function type(): string {
82: return strtoupper($this->parameters->get('CUTYPE') ?? 'INDIVIDUAL');
83: }
84:
85: public function rsvp(): bool {
86: return strtoupper($this->parameters->get('RSVP') ?? 'FALSE') === 'TRUE';
87: }
88:
89: public function __toString(): string {
90: return $this->uri;
91: }
92: }
93: