Issues (1502)

lib/syncobjects/syncappointment.php (3 issues)

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * WBXML appointment entities that can be parsed directly (as a stream) from
9
 * WBXML. It is automatically decoded according to $mapping and the Sync
10
 * WBXML mappings.
11
 */
12
13
class SyncAppointment extends SyncObject {
14
	public $timezone;
15
	public $dtstamp;
16
	public $starttime;
17
	public $subject;
18
	public $uid;
19
	public $organizername;
20
	public $organizeremail;
21
	public $location;
22
	public $location2; // AS 16: SyncLocation object
23
	public $endtime;
24
	public $recurrence;
25
	public $sensitivity;
26
	public $busystatus;
27
	public $alldayevent;
28
	public $reminder;
29
	public $rtf;
30
	public $meetingstatus;
31
	public $attendees;
32
	public $body;
33
	public $bodytruncated;
34
	public $exceptions;
35
	public $deleted;
36
	public $exceptionstarttime;
37
	public $categories;
38
39
	// AS 12.0 props
40
	public $asbody;
41
	public $nativebodytype;
42
43
	// AS 14.0 props
44
	public $disallownewtimeprop;
45
	public $responsetype;
46
	public $responserequested;
47
48
	// AS 14.1 props
49
	public $onlineMeetingConfLink;
50
	public $onlineMeetingExternalLink;
51
52
	// AS 16.0 props
53
	public $asattachments;
54
	public $clientuid;
55
	public $instanceid;
56
	public $instanceiddelete;
57
58
	public function __construct() {
59
		$mapping = [
60
			SYNC_POOMCAL_TIMEZONE => [
61
				self::STREAMER_VAR => "timezone",
62
				self::STREAMER_RONOTIFY => true,
63
			],
64
			SYNC_POOMCAL_DTSTAMP => [
65
				self::STREAMER_VAR => "dtstamp",
66
				self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
67
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO],
68
			],
69
			SYNC_POOMCAL_STARTTIME => [
70
				self::STREAMER_VAR => "starttime",
71
				self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
72
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_CMPLOWER => SYNC_POOMCAL_ENDTIME],
73
				self::STREAMER_RONOTIFY => true,
74
			],
75
			SYNC_POOMCAL_SUBJECT => [
76
				self::STREAMER_VAR => "subject",
77
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY],
78
				self::STREAMER_RONOTIFY => true,
79
				self::STREAMER_PRIVATE => self::STRIP_PRIVATE_SUBSTITUTE,
80
			],
81
			SYNC_POOMCAL_UID => [self::STREAMER_VAR => "uid"],
82
			SYNC_POOMCAL_ORGANIZERNAME => [
83
				self::STREAMER_VAR => "organizername", // verified below
84
				self::STREAMER_PRIVATE => 'Undisclosed Organizer',
85
			],
86
			SYNC_POOMCAL_ORGANIZEREMAIL => [
87
				self::STREAMER_VAR => "organizeremail", // verified below
88
				self::STREAMER_PRIVATE => 'undisclosed@localhost',
89
			],
90
			SYNC_POOMCAL_LOCATION => [
91
				self::STREAMER_VAR => "location",
92
				self::STREAMER_RONOTIFY => true,
93
				self::STREAMER_PRIVATE => true,
94
			],
95
			SYNC_POOMCAL_ENDTIME => [
96
				self::STREAMER_VAR => "endtime",
97
				self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
98
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_CMPHIGHER => SYNC_POOMCAL_STARTTIME],
99
				self::STREAMER_RONOTIFY => true,
100
			],
101
			SYNC_POOMCAL_RECURRENCE => [
102
				self::STREAMER_VAR => "recurrence",
103
				self::STREAMER_TYPE => "SyncRecurrence",
104
				self::STREAMER_RONOTIFY => true,
105
			],
106
			// Sensitivity values
107
			// 0 = Normal
108
			// 1 = Personal
109
			// 2 = Private
110
			// 3 = Confident
111
			SYNC_POOMCAL_SENSITIVITY => [
112
				self::STREAMER_VAR => "sensitivity",
113
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_ONEVALUEOF => [0, 1, 2, 3]],
114
				self::STREAMER_RONOTIFY => true,
115
				self::STREAMER_VALUEMAP => [0 => "Normal",
116
					1 => "Personal",
117
					2 => "Private",
118
					3 => "Confident",
119
				],
120
			],
121
			// Busystatus values
122
			// 0 = Free
123
			// 1 = Tentative
124
			// 2 = Busy
125
			// 3 = Out of office
126
			// 4 = Working Elsewhere
127
			SYNC_POOMCAL_BUSYSTATUS => [
128
				self::STREAMER_VAR => "busystatus",
129
				self::STREAMER_CHECKS => [
130
					self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
131
					self::STREAMER_CHECK_ONEVALUEOF => [0, 1, 2, 3, 4],
132
				],
133
				self::STREAMER_RONOTIFY => true,
134
				// if private is stripped, value will be set to 2 (busy)
135
				self::STREAMER_PRIVATE => 2,
136
				self::STREAMER_VALUEMAP => [
137
					0 => "Free",
138
					1 => "Tentative",
139
					2 => "Busy",
140
					3 => "Out of office",
141
					4 => "Working Elsewhere",
142
				],
143
			],
144
			SYNC_POOMCAL_ALLDAYEVENT => [
145
				self::STREAMER_VAR => "alldayevent",
146
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO],
147
				self::STREAMER_RONOTIFY => true,
148
				self::STREAMER_VALUEMAP => [
149
					0 => "No",
150
					1 => "Yes",
151
				],
152
			],
153
			SYNC_POOMCAL_REMINDER => [
154
				self::STREAMER_VAR => "reminder",
155
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_CMPHIGHER => -1],
156
				self::STREAMER_RONOTIFY => true,
157
				// if private is stripped, value will be unset (no reminder)
158
				self::STREAMER_PRIVATE => true,
159
			],
160
			// Meetingstatus values
161
			//  0 = is not a meeting
162
			//  1 = is a meeting
163
			//  3 = Meeting received
164
			//  5 = Meeting is canceled
165
			//  7 = Meeting is canceled and received
166
			//  9 = as 1
167
			// 11 = as 3
168
			// 13 = as 5
169
			// 15 = as 7
170
			SYNC_POOMCAL_MEETINGSTATUS => [
171
				self::STREAMER_VAR => "meetingstatus",
172
				self::STREAMER_CHECKS => [self::STREAMER_CHECK_ONEVALUEOF => [0, 1, 3, 5, 7, 9, 11, 13, 15]],
173
				self::STREAMER_RONOTIFY => true,
174
				self::STREAMER_VALUEMAP => [
175
					0 => "Not a meeting",
176
					1 => "Meeting",
177
					3 => "Meeting received",
178
					5 => "Meeting canceled",
179
					7 => "Meeting canceled and received",
180
					9 => "Meeting",
181
					11 => "Meeting received",
182
					13 => "Meeting canceled",
183
					15 => "Meeting canceled and received",
184
				],
185
			],
186
			SYNC_POOMCAL_ATTENDEES => [
187
				self::STREAMER_VAR => "attendees",
188
				self::STREAMER_TYPE => "SyncAttendee",
189
				self::STREAMER_ARRAY => SYNC_POOMCAL_ATTENDEE,
190
				self::STREAMER_RONOTIFY => true,
191
				self::STREAMER_PRIVATE => true,
192
			],
193
			SYNC_POOMCAL_BODY => [
194
				self::STREAMER_VAR => "body",
195
				self::STREAMER_RONOTIFY => true,
196
				self::STREAMER_PRIVATE => true,
197
			],
198
			SYNC_POOMCAL_BODYTRUNCATED => [
199
				self::STREAMER_VAR => "bodytruncated",
200
				self::STREAMER_PRIVATE => true,
201
			],
202
			SYNC_POOMCAL_EXCEPTIONS => [
203
				self::STREAMER_VAR => "exceptions",
204
				self::STREAMER_TYPE => "SyncAppointmentException",
205
				self::STREAMER_ARRAY => SYNC_POOMCAL_EXCEPTION,
206
				self::STREAMER_RONOTIFY => true,
207
			],
208
			SYNC_POOMCAL_CATEGORIES => [
209
				self::STREAMER_VAR => "categories",
210
				self::STREAMER_ARRAY => SYNC_POOMCAL_CATEGORY,
211
				self::STREAMER_RONOTIFY => true,
212
				self::STREAMER_PRIVATE => true,
213
			],
214
		];
215
216
		if (Request::GetProtocolVersion() >= 12.0) {
217
			$mapping[SYNC_AIRSYNCBASE_BODY] = [
218
				self::STREAMER_VAR => "asbody",
219
				self::STREAMER_TYPE => "SyncBaseBody",
220
				self::STREAMER_RONOTIFY => true,
221
				self::STREAMER_PRIVATE => true,
222
			];
223
224
			$mapping[SYNC_AIRSYNCBASE_NATIVEBODYTYPE] = [self::STREAMER_VAR => "nativebodytype"];
225
226
			// unset these properties because airsyncbase body and attachments will be used instead
227
			unset($mapping[SYNC_POOMCAL_BODY], $mapping[SYNC_POOMCAL_BODYTRUNCATED]);
228
		}
229
230
		if (Request::GetProtocolVersion() >= 14.0) {
231
			$mapping[SYNC_POOMCAL_DISALLOWNEWTIMEPROPOSAL] = [
232
				self::STREAMER_VAR => "disallownewtimeprop",
233
				self::STREAMER_RONOTIFY => true,
234
				self::STREAMER_PRIVATE => 1,
235
			]; // don't permit new time proposal
236
			$mapping[SYNC_POOMCAL_RESPONSEREQUESTED] = [
237
				self::STREAMER_VAR => "responserequested",
238
				self::STREAMER_RONOTIFY => true,
239
			];
240
			$mapping[SYNC_POOMCAL_RESPONSETYPE] = [
241
				self::STREAMER_VAR => "responsetype",
242
				self::STREAMER_RONOTIFY => true,
243
			];
244
		}
245
246
		if (Request::GetProtocolVersion() >= 14.1) {
247
			$mapping[SYNC_POOMCAL_ONLINEMEETINGCONFLINK] = [
248
				self::STREAMER_VAR => "onlineMeetingConfLink",
249
				self::STREAMER_RONOTIFY => true,
250
			];
251
			$mapping[SYNC_POOMCAL_ONLINEMEETINGEXTERNALLINK] = [
252
				self::STREAMER_VAR => "onlineMeetingExternalLink",
253
				self::STREAMER_RONOTIFY => true,
254
			];
255
		}
256
257
		if (Request::GetProtocolVersion() >= 16.0) {
258
			$mapping[SYNC_AIRSYNCBASE_ATTACHMENTS] = [
259
				self::STREAMER_VAR => "asattachments",
260
				// Different tags can be used to encapsulate the SyncBaseAttachmentSubtypes depending on its usecase
261
				self::STREAMER_ARRAY => [
262
					SYNC_AIRSYNCBASE_ATTACHMENT => "SyncBaseAttachment",
263
					SYNC_AIRSYNCBASE_ADD => "SyncBaseAttachmentAdd",
264
					SYNC_AIRSYNCBASE_DELETE => "SyncBaseAttachmentDelete",
265
				],
266
			];
267
			$mapping[SYNC_AIRSYNCBASE_LOCATION] = [
268
				self::STREAMER_VAR => "location2",
269
				self::STREAMER_TYPE => "SyncLocation",
270
				self::STREAMER_RONOTIFY => true,
271
			];
272
			$mapping[SYNC_POOMCAL_CLIENTUID] = [
273
				self::STREAMER_VAR => "clientuid",
274
				self::STREAMER_RONOTIFY => true,
275
			];
276
			// Placeholder for the InstanceId (recurrence exceptions) and its deletion request
277
			$mapping[SYNC_AIRSYNCBASE_INSTANCEID] = [
278
				self::STREAMER_VAR => "instanceid",
279
				self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE,
280
			];
281
			$mapping[SYNC_AIRSYNCBASE_INSTANCEID_DELETE] = [
282
				self::STREAMER_VAR => "instanceiddelete",
283
				self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE,
284
			];
285
286
			// unset these properties because airsyncbase location will be used instead
287
			unset($mapping[SYNC_POOMCAL_LOCATION]);
288
		}
289
290
		parent::__construct($mapping);
291
292
		// Indicates that this SyncObject supports the private flag and stripping of private data.
293
		$this->supportsPrivateStripping = true;
294
	}
295
296
	/**
297
	 * Method checks if the object has the minimum of required parameters
298
	 * and fulfills semantic dependencies.
299
	 *
300
	 * This overloads the general check() with special checks to be executed
301
	 * Checks if SYNC_POOMCAL_ORGANIZERNAME and SYNC_POOMCAL_ORGANIZEREMAIL are correctly set
302
	 *
303
	 * @param bool $logAsDebug (opt) default is false, so messages are logged in WARN log level
304
	 *
305
	 * @return bool
306
	 */
307
	#[Override]
308
	public function Check($logAsDebug = false) {
309
		// Fix starttime and endtime if they are not set on NEW appointments.
310
		if ($this->flags === SYNC_NEWMESSAGE) {
0 ignored issues
show
The condition $this->flags === SYNC_NEWMESSAGE is always false.
Loading history...
311
			$time = time();
312
			$calcstart = $time + 1800 - ($time % 1800); // round up to the next half hour
313
314
			// Check error cases first
315
			// Case 2: starttime not set, endtime in the past
316
			if (!isset($this->starttime) && isset($this->endtime) && $this->endtime < $time) {
317
				SLog::Write(LOGLEVEL_WARN, "SyncAppointment->Check(): Parameter 'starttime' not set while 'endtime' is in the past (case 2). Aborting.");
318
319
				return false;
320
			}
321
			// Case 3b: starttime not set, endtime in the future (3) but before the calculated starttime (3b)
322
			if (!isset($this->starttime) && isset($this->endtime) && $this->endtime > $time && $this->endtime < $calcstart) {
323
				SLog::Write(LOGLEVEL_WARN, "SyncAppointment->Check(): Parameter 'starttime' not set while 'endtime' is in the future but before the calculated starttime (case 3b). Aborting.");
324
325
				return false;
326
			}
327
			// Case 5: starttime in the future but no endtime set
328
			if (isset($this->starttime) && $this->starttime > $time && !isset($this->endtime)) {
329
				SLog::Write(LOGLEVEL_WARN, "SyncAppointment->Check(): Parameter 'starttime' is in the future but 'endtime' is not set (case 5). Aborting.");
330
331
				return false;
332
			}
333
334
			// Set starttime to the rounded up next half hour
335
			// Case 1, 3a (endtime won't be changed as it's set)
336
			if (!isset($this->starttime)) {
337
				$this->starttime = $calcstart;
338
				SLog::Write(LOGLEVEL_WBXML, sprintf("SyncAppointment->Check(): Parameter 'starttime' was not set, setting it to %d (%s).", $this->starttime, Utils::FormatDate($this->starttime)));
339
			}
340
			// Case 1, 4
341
			if (!isset($this->endtime)) {
342
				$this->endtime = $calcstart + 1800; // 30 min after calcstart
343
				SLog::Write(LOGLEVEL_WBXML, sprintf("SyncAppointment->Check(): Parameter 'endtime' was not set, setting it to %d (%s).", $this->endtime, Utils::FormatDate($this->endtime)));
344
			}
345
		}
346
347
		$ret = parent::Check($logAsDebug);
348
349
		// semantic checks general "turn off switch"
350
		if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false) {
0 ignored issues
show
The constant DO_SEMANTIC_CHECKS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
351
			return $ret;
352
		}
353
354
		if (!$ret) {
355
			return false;
356
		}
357
358
		if ($this->meetingstatus > 0) {
359
			if (!isset($this->organizername) || !isset($this->organizeremail)) {
360
				SLog::Write(LOGLEVEL_INFO, "SyncAppointment->Check(): Parameter 'organizername' and 'organizeremail' should be set for a meeting request");
361
			}
362
		}
363
364
		// do not sync a recurrent appointment without a timezone (except all day events)
365
		if (isset($this->recurrence) && !isset($this->timezone) && empty($this->alldayevent)) {
366
			SLog::Write(LOGLEVEL_INFO, "SyncAppointment->Check(): timezone for a recurring appointment is not set.");
367
368
			return false;
369
		}
370
371
		if (isset($this->busystatus) && $this->busystatus == 0xFFFFFFFF) {
372
			SLog::Write(LOGLEVEL_INFO, "SyncAppointment->Check(): rewriting busystatus -1 (0xFFFFFFFF) to fbBusy (2).");
373
			$this->busystatus = fbBusy;
0 ignored issues
show
The constant fbBusy was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
374
		}
375
376
		return true;
377
	}
378
}
379
380
class SyncAppointmentResponse extends SyncAppointment {
381
	use ResponseTrait;
382
}
383