Issues (1513)

lib/syncobjects/syncappointment.php (3 issues)

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