1: <?php
2: declare(strict_types=1);
3:
4: namespace om\ICal;
5:
6: use DateInterval;
7: use DateTimeInterface;
8: use InvalidArgumentException;
9: use om\ICal\Value\CalAddress;
10: use om\ICal\Value\Classification;
11: use om\ICal\Value\Conference;
12: use om\ICal\Value\DateTimeValue;
13: use om\ICal\Value\Image;
14: use om\ICal\Value\Link;
15: use om\ICal\Value\Period;
16: use om\ICal\Value\PropertyFactory;
17: use om\ICal\Value\Relation;
18: use om\ICal\Value\Status;
19: use om\RRule\Rule;
20:
21: /**
22: * VTODO (RFC 5545, section 3.6.2).
23: *
24: * @phpstan-import-type PropertyList from PropertyFactory
25: */
26: final class Todo extends Item {
27: /**
28: * A new task; the arguments are those of Event::new() with DUE instead of DTEND.
29: *
30: * @param ?string $uid UID, a random UUID when null
31: * @param DateTimeInterface|DateTimeValue|string|null $stamp DTSTAMP, now when null; written in UTC
32: * @param DateTimeInterface|DateTimeValue|string|null $start DTSTART: a DateTimeInterface (TZID of its IANA timezone or UTC), a DateTimeValue (also a DATE or a floating time) or an iCalendar string
33: * @param DateTimeInterface|DateTimeValue|string|null $due DUE, not earlier than the start and of the same value type
34: * @param DateInterval|string|null $duration DURATION instead of DUE, requires a start
35: * @param DateTimeInterface|DateTimeValue|string|null $completed COMPLETED, written in UTC
36: * @param ?int $percentComplete PERCENT-COMPLETE, 0 to 100
37: * @param Status|string|null $status NEEDS-ACTION, COMPLETED, IN-PROCESS or CANCELLED
38: * @param Classification|string|null $classification
39: * @param iterable<string> $categories
40: * @param CalAddress|string|null $organizer
41: * @param iterable<CalAddress|string> $attendees
42: * @param Rule|string|null $rrule
43: * @param iterable<DateTimeInterface|DateTimeValue|Period|string> $rdates
44: * @param iterable<DateTimeInterface|DateTimeValue|string> $exdates
45: * @param DateTimeInterface|DateTimeValue|string|null $recurrenceId
46: * @param array{float|int, float|int}|null $geo
47: * @param iterable<Image|string> $images
48: * @param iterable<Conference|string> $conferences
49: * @param iterable<Link|string> $links
50: * @param iterable<Relation|string> $relatedTo
51: * @param iterable<Alarm|Component> $alarms
52: * @param iterable<Location|Component> $locations
53: * @param PropertyList $properties
54: * @throws InvalidArgumentException for an invalid value or combination of arguments
55: */
56: public static function new(
57: ?string $uid = null,
58: DateTimeInterface|DateTimeValue|string|null $stamp = null,
59: DateTimeInterface|DateTimeValue|string|null $start = null,
60: DateTimeInterface|DateTimeValue|string|null $due = null,
61: DateInterval|string|null $duration = null,
62: DateTimeInterface|DateTimeValue|string|null $completed = null,
63: ?int $percentComplete = null,
64: ?string $summary = null,
65: ?string $description = null,
66: ?string $location = null,
67: ?string $url = null,
68: Status|string|null $status = null,
69: Classification|string|null $classification = null,
70: ?int $priority = null,
71: ?int $sequence = null,
72: iterable $categories = [],
73: CalAddress|string|null $organizer = null,
74: iterable $attendees = [],
75: Rule|string|null $rrule = null,
76: iterable $rdates = [],
77: iterable $exdates = [],
78: DateTimeInterface|DateTimeValue|string|null $recurrenceId = null,
79: ?array $geo = null,
80: ?string $color = null,
81: iterable $images = [],
82: iterable $conferences = [],
83: iterable $links = [],
84: iterable $relatedTo = [],
85: iterable $alarms = [],
86: iterable $locations = [],
87: array $properties = [],
88: ): self {
89: $builder = ComponentBuilder::item('VTODO', $uid, $stamp);
90: $startValue = $builder->start($start);
91: $dueValue = $builder->end('DUE', $due, $duration, $startValue);
92: $builder->recurrence($startValue, $rrule, $rdates, $exdates, $recurrenceId)
93: ->add($completed === null ? null : PropertyFactory::dateTime('COMPLETED', $completed))
94: ->integer('PERCENT-COMPLETE', $percentComplete, 0, 100)
95: ->descriptive($summary, $description, $location, $url, $classification, $priority, $sequence, $categories, $organizer, $attendees, $geo, $color, $images, $conferences, $links, $relatedTo)
96: ->status($status)
97: ->alarms($alarms, $startValue !== null, $dueValue !== null, 'DUE')
98: ->locations($locations);
99: return new self($builder->build($properties), new Calendar());
100: }
101:
102: /**
103: * DUE, or DTSTART + DURATION.
104: */
105: public function due(): ?DateTimeValue {
106: return $this->date('DUE') ?? ($this->has('DURATION') ? parent::end() : null);
107: }
108:
109: public function end(): ?DateTimeValue {
110: return $this->due() ?? $this->start();
111: }
112:
113: public function duration(): DateInterval {
114: $start = $this->start();
115: $due = $this->date('DUE');
116: if ($start !== null && $due !== null) {
117: return self::between($start, $due);
118: }
119: $duration = $this->property('DURATION');
120: return ($duration === null ? null : $this->calendar->values()->duration($duration)) ?? new DateInterval('PT0S');
121: }
122:
123: public function completed(): ?DateTimeValue {
124: return $this->date('COMPLETED');
125: }
126:
127: public function percentComplete(): ?int {
128: return $this->integer('PERCENT-COMPLETE');
129: }
130:
131: public function isCompleted(): bool {
132: return $this->status() === 'COMPLETED' || $this->has('COMPLETED');
133: }
134: }
135: