Passed
Push — master ( 7a8062...ff661f )
by
unknown
13:06
created

class.recurrence.php (134 issues)

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6
 */
7
8
/**
9
 * Recurrence.
10
 */
11
class Recurrence extends BaseRecurrence {
12
	/*
13
	 * ABOUT TIMEZONES
14
	 *
15
	 * Timezones are rather complicated here so here are some rules to think about:
16
	 *
17
	 * - Timestamps in mapi-like properties (so in PT_SYSTIME properties) are always in GMT (including
18
	 *   the 'basedate' property in exceptions !!)
19
	 * - Timestamps for recurrence (so start/end of recurrence, and basedates for exceptions (everything
20
	 *   outside the 'basedate' property in the exception !!), and start/endtimes for exceptions) are
21
	 *   always in LOCAL time.
22
	 */
23
24
	// All properties for a recipient that are interesting
25
	public $recipprops = [
26
		PR_ENTRYID,
27
		PR_SEARCH_KEY,
0 ignored issues
show
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
28
		PR_DISPLAY_NAME,
0 ignored issues
show
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
29
		PR_EMAIL_ADDRESS,
0 ignored issues
show
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
30
		PR_RECIPIENT_ENTRYID,
0 ignored issues
show
The constant PR_RECIPIENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
31
		PR_RECIPIENT_TYPE,
0 ignored issues
show
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
32
		PR_SEND_INTERNET_ENCODING,
0 ignored issues
show
The constant PR_SEND_INTERNET_ENCODING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
33
		PR_SEND_RICH_INFO,
0 ignored issues
show
The constant PR_SEND_RICH_INFO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
34
		PR_RECIPIENT_DISPLAY_NAME,
0 ignored issues
show
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
35
		PR_ADDRTYPE,
0 ignored issues
show
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
36
		PR_DISPLAY_TYPE,
0 ignored issues
show
The constant PR_DISPLAY_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
37
		PR_DISPLAY_TYPE_EX,
0 ignored issues
show
The constant PR_DISPLAY_TYPE_EX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
38
		PR_RECIPIENT_TRACKSTATUS,
0 ignored issues
show
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
39
		PR_RECIPIENT_TRACKSTATUS_TIME,
0 ignored issues
show
The constant PR_RECIPIENT_TRACKSTATUS_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
40
		PR_RECIPIENT_FLAGS,
0 ignored issues
show
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
41
		PR_ROWID,
0 ignored issues
show
The constant PR_ROWID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
42
	];
43
44
	/**
45
	 * Constructor.
46
	 *
47
	 * @param resource $store    MAPI Message Store Object
48
	 * @param mixed    $message  the MAPI (appointment) message
49
	 * @param array    $proptags an associative array of protags and their values
50
	 */
51
	public function __construct($store, $message, $proptags = []) {
52
		if (!empty($proptags)) {
53
			$this->proptags = $proptags;
54
		}
55
		else {
56
			$properties = [];
57
			$properties["entryid"] = PR_ENTRYID;
58
			$properties["parent_entryid"] = PR_PARENT_ENTRYID;
0 ignored issues
show
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
59
			$properties["message_class"] = PR_MESSAGE_CLASS;
0 ignored issues
show
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
60
			$properties["icon_index"] = PR_ICON_INDEX;
0 ignored issues
show
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
61
			$properties["subject"] = PR_SUBJECT;
0 ignored issues
show
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
62
			$properties["display_to"] = PR_DISPLAY_TO;
0 ignored issues
show
The constant PR_DISPLAY_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
63
			$properties["importance"] = PR_IMPORTANCE;
0 ignored issues
show
The constant PR_IMPORTANCE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
64
			$properties["sensitivity"] = PR_SENSITIVITY;
0 ignored issues
show
The constant PR_SENSITIVITY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
65
			$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:" . PidLidAppointmentStartWhole;
0 ignored issues
show
The constant PidLidAppointmentStartWhole was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
66
			$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:" . PidLidAppointmentEndWhole;
0 ignored issues
show
The constant PidLidAppointmentEndWhole was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
67
			$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:" . PidLidRecurring;
0 ignored issues
show
The constant PidLidRecurring was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
68
			$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:" . PidLidAppointmentRecur;
0 ignored issues
show
The constant PidLidAppointmentRecur was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
69
			$properties["busystatus"] = "PT_LONG:PSETID_Appointment:" . PidLidBusyStatus;
0 ignored issues
show
The constant PidLidBusyStatus was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
70
			$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
71
			$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:" . PidLidAppointmentSubType;
0 ignored issues
show
The constant PidLidAppointmentSubType was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
72
			$properties["private"] = "PT_BOOLEAN:PSETID_Common:" . PidLidPrivate;
0 ignored issues
show
The constant PidLidPrivate was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
73
			$properties["meeting"] = "PT_LONG:PSETID_Appointment:" . PidLidAppointmentStateFlags;
0 ignored issues
show
The constant PidLidAppointmentStateFlags was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
74
			$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:" . PidLidClipStart;
0 ignored issues
show
The constant PidLidClipStart was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
75
			$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:" . PidLidClipEnd;
0 ignored issues
show
The constant PidLidClipEnd was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
76
			$properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
77
			$properties["location"] = "PT_STRING8:PSETID_Appointment:" . PidLidLocation;
0 ignored issues
show
The constant PidLidLocation was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
78
			$properties["duration"] = "PT_LONG:PSETID_Appointment:" . PidLidAppointmentDuration;
0 ignored issues
show
The constant PidLidAppointmentDuration was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
79
			$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
80
			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:" . PidLidReminderSet;
0 ignored issues
show
The constant PidLidReminderSet was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
81
			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:" . PidLidReminderDelta;
0 ignored issues
show
The constant PidLidReminderDelta was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
82
			$properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
83
			$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
84
			$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
85
			$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
86
			$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderTime;
0 ignored issues
show
The constant PidLidReminderTime was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
87
			$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
88
			$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
89
			$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:" . PidLidExceptionReplaceTime;
0 ignored issues
show
The constant PidLidExceptionReplaceTime was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
90
			$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:" . PidLidTimeZoneStruct;
0 ignored issues
show
The constant PidLidTimeZoneStruct was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
91
			$properties["timezone"] = "PT_STRING8:PSETID_Appointment:" . PidLidTimeZoneDescription;
0 ignored issues
show
The constant PidLidTimeZoneDescription was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
92
			$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderSignalTime;
0 ignored issues
show
The constant PidLidReminderSignalTime was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
93
			$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
94
			$properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:" . PidLidSmartNoAttach;
0 ignored issues
show
The constant PidLidSmartNoAttach was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
95
96
			$this->proptags = getPropIdsFromStrings($store, $properties);
97
		}
98
99
		parent::__construct($store, $message);
100
	}
101
102
	/**
103
	 * Create an exception.
104
	 *
105
	 * @param array $exception_props  the exception properties (same properties as normal recurring items)
106
	 * @param mixed $base_date        the base date of the exception (LOCAL time of non-exception occurrence)
107
	 * @param bool  $delete           true - delete occurrence, false - create new exception or modify existing
108
	 * @param array $exception_recips list of recipients
109
	 * @param mixed $copy_attach_from mapi message from which attachments should be copied
110
	 */
111
	public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false): bool {
112
		$baseday = $this->dayStartOf($base_date);
113
		$basetime = $baseday + $this->recur["startocc"] * 60;
114
115
		// Remove any pre-existing exception on this base date
116
		if ($this->isException($baseday)) {
117
			$this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
118
		}
119
120
		if (!$delete) {
121
			if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
122
				return false;
123
			}
124
			// Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
125
			foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) {
126
				if (isset($this->messageprops[$this->proptags[$propname]])) {
127
					$props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
128
				}
129
			}
130
131
			$props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
0 ignored issues
show
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
132
			unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]);
0 ignored issues
show
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
133
134
			$props = $exception_props + $props;
135
136
			// Basedate in the exception attachment is the GMT time at which the original occurrence would have been
137
			$props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
138
139
			if (!isset($exception_props[$this->proptags["startdate"]])) {
140
				$props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
141
			}
142
143
			if (!isset($exception_props[$this->proptags["duedate"]])) {
144
				$props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
145
			}
146
147
			// synchronize commonstart/commonend with startdate/duedate
148
			if (isset($props[$this->proptags["startdate"]])) {
149
				$props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
150
			}
151
152
			if (isset($props[$this->proptags["duedate"]])) {
153
				$props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
154
			}
155
156
			// Save the data into an attachment
157
			$this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
0 ignored issues
show
It seems like $copy_attach_from can also be of type false; however, parameter $copy_attach_from of Recurrence::createExceptionAttachment() does only seem to accept mapi_message, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
			$this->createExceptionAttachment($props, $exception_recips, /** @scrutinizer ignore-type */ $copy_attach_from);
Loading history...
158
159
			$changed_item = [];
160
161
			$changed_item["basedate"] = $baseday;
162
			$changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
163
			$changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
164
165
			if (array_key_exists($this->proptags["subject"], $exception_props)) {
166
				$changed_item["subject"] = $exception_props[$this->proptags["subject"]];
167
			}
168
169
			if (array_key_exists($this->proptags["location"], $exception_props)) {
170
				$changed_item["location"] = $exception_props[$this->proptags["location"]];
171
			}
172
173
			if (array_key_exists($this->proptags["label"], $exception_props)) {
174
				$changed_item["label"] = $exception_props[$this->proptags["label"]];
175
			}
176
177
			if (array_key_exists($this->proptags["reminder"], $exception_props)) {
178
				$changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
179
			}
180
181
			if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
182
				$changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
183
			}
184
185
			if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
186
				$changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
187
			}
188
189
			if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
190
				$changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
191
			}
192
193
			// Add the changed occurrence to the list
194
			array_push($this->recur["changed_occurrences"], $changed_item);
195
		}
196
		else {
197
			// Delete the occurrence by placing it in the deleted occurrences list
198
			array_push($this->recur["deleted_occurrences"], $baseday);
199
		}
200
201
		// Turn on hideattachments, because the attachments in this item are the exceptions
202
		mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
0 ignored issues
show
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
		/** @scrutinizer ignore-call */ 
203
  mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
Loading history...
203
204
		// Save recurrence data to message
205
		$this->saveRecurrence();
206
207
		return true;
208
	}
209
210
	/**
211
	 * Modifies an existing exception, but only updates the given properties
212
	 * NOTE: You can't remove properties from an exception, only add new ones.
213
	 *
214
	 * @param mixed $exception_props
215
	 * @param mixed $base_date
216
	 * @param mixed $exception_recips
217
	 * @param mixed $copy_attach_from
218
	 */
219
	public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false): bool {
220
		if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
221
			return false;
222
		}
223
224
		$baseday = $this->dayStartOf($base_date);
225
		$extomodify = false;
226
227
		for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) {
228
			if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) {
229
				$extomodify = &$this->recur["changed_occurrences"][$i];
230
			}
231
		}
232
233
		if (!$extomodify) {
0 ignored issues
show
$extomodify is of type mixed, thus it always evaluated to false.
Loading history...
234
			return false;
235
		}
236
237
		// remove basedate property as we want to preserve the old value
238
		// client will send basedate with time part as zero, so discard that value
239
		unset($exception_props[$this->proptags["basedate"]]);
240
241
		if (array_key_exists($this->proptags["startdate"], $exception_props)) {
242
			$extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
243
		}
244
245
		if (array_key_exists($this->proptags["duedate"], $exception_props)) {
246
			$extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
247
		}
248
249
		if (array_key_exists($this->proptags["subject"], $exception_props)) {
250
			$extomodify["subject"] = $exception_props[$this->proptags["subject"]];
251
		}
252
253
		if (array_key_exists($this->proptags["location"], $exception_props)) {
254
			$extomodify["location"] = $exception_props[$this->proptags["location"]];
255
		}
256
257
		if (array_key_exists($this->proptags["label"], $exception_props)) {
258
			$extomodify["label"] = $exception_props[$this->proptags["label"]];
259
		}
260
261
		if (array_key_exists($this->proptags["reminder"], $exception_props)) {
262
			$extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
263
		}
264
265
		if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
266
			$extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
267
		}
268
269
		if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
270
			$extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
271
		}
272
273
		if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
274
			$extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
275
		}
276
277
		$exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
0 ignored issues
show
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
278
279
		// synchronize commonstart/commonend with startdate/duedate
280
		if (isset($exception_props[$this->proptags["startdate"]])) {
281
			$exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
282
		}
283
284
		if (isset($exception_props[$this->proptags["duedate"]])) {
285
			$exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
286
		}
287
288
		$attach = $this->getExceptionAttachment($baseday);
289
		if (!$attach) {
290
			if ($copy_attach_from) {
291
				$this->deleteExceptionAttachment($base_date);
292
				$this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
293
			}
294
			else {
295
				$this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
296
			}
297
		}
298
		else {
299
			$message = mapi_attach_openobj($attach, MAPI_MODIFY);
0 ignored issues
show
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

299
			$message = /** @scrutinizer ignore-call */ mapi_attach_openobj($attach, MAPI_MODIFY);
Loading history...
300
301
			// Set exception properties on embedded message and save
302
			mapi_setprops($message, $exception_props);
0 ignored issues
show
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

302
			/** @scrutinizer ignore-call */ 
303
   mapi_setprops($message, $exception_props);
Loading history...
303
			$this->setExceptionRecipients($message, $exception_recips, false);
304
			mapi_savechanges($message);
0 ignored issues
show
The function mapi_savechanges was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

304
			/** @scrutinizer ignore-call */ 
305
   mapi_savechanges($message);
Loading history...
305
306
			// If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
307
			// on the attachment which holds the embedded msg and save everything.
308
			$props = [];
309
			if (isset($exception_props[$this->proptags["startdate"]])) {
310
				$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
0 ignored issues
show
The constant PR_EXCEPTION_STARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
311
			}
312
			if (isset($exception_props[$this->proptags["duedate"]])) {
313
				$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
0 ignored issues
show
The constant PR_EXCEPTION_ENDTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
314
			}
315
			if (!empty($props)) {
316
				mapi_setprops($attach, $props);
317
			}
318
319
			mapi_savechanges($attach);
320
		}
321
322
		// Save recurrence data to message
323
		$this->saveRecurrence();
324
325
		return true;
326
	}
327
328
	// Checks to see if the following is true:
329
	// 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
330
	// 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
331
	//
332
	// Both $basedate and $start are in LOCAL time
333
	public function isValidExceptionDate($basedate, $start): bool {
334
		// The way we do this is to look at the days that we're 'moving' the item in the exception. Each
335
		// of these days may only contain the item that we're modifying. Any other item violates the rules.
336
337
		if ($this->isException($basedate)) {
338
			// If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
339
			// the exception used to be.
340
			$oldexception = $this->getChangeException($basedate);
341
			$prevday = $this->dayStartOf($oldexception["start"]);
342
		}
343
		else {
344
			// If its a new exception, we want to look at the original placement of this item.
345
			$prevday = $basedate;
346
		}
347
348
		$startday = $this->dayStartOf($start);
349
350
		// Get all the occurrences on the days between the basedate (may be reversed)
351
		if ($prevday < $startday) {
352
			$items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
353
		}
354
		else {
355
			$items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
356
		}
357
358
		// There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
359
		// then we abort the change, since one of the rules has been violated.
360
		return count($items) == 1;
361
	}
362
363
	/**
364
	 * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:.
365
	 *
366
	 * Both must be true:
367
	 * - reminder time of this item is not before the starttime of the previous recurring item
368
	 * - reminder time of the next item is not before the starttime of this item
369
	 *
370
	 * @param date   $basedate        the base date of the exception (LOCAL time of non-exception occurrence)
371
	 * @param string $reminderminutes reminder minutes which is set of the item
372
	 * @param date   $startdate       the startdate of the selected item
373
	 *
374
	 * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
375
	 */
376
	public function isValidReminderTime($basedate, $reminderminutes, $startdate): bool {
377
		// get all occurrence items before the selected items occurrence starttime
378
		$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
0 ignored issues
show
It seems like $this->toGMT($this->tz, $basedate) can also be of type date; however, parameter $end of BaseRecurrence::getItems() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

378
		$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], /** @scrutinizer ignore-type */ $this->toGMT($this->tz, $basedate));
Loading history...
379
380
		if (!empty($occitems)) {
381
			// as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
382
			$previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
383
384
			// if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
385
			if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
386
				return false;
387
			}
388
		}
389
390
		// Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
391
		$currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
0 ignored issues
show
It seems like $this->toGMT($this->tz, $basedate) can also be of type date; however, parameter $start of BaseRecurrence::getItems() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

391
		$currentOcc = $this->getItems(/** @scrutinizer ignore-type */ $this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
Loading history...
392
393
		// If there are another two occurrences, then the first is the current occurrence, and the one after that
394
		// is the next occurrence.
395
		if (count($currentOcc) > 1) {
396
			$next = $currentOcc[1];
397
			// Get reminder time of the next occurrence.
398
			$nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
399
			// If the reminder time of the next item is before the start of this item, then that's not allowed
400
			if ($nextOccReminderTime <= $startdate) {
401
				return false;
402
			}
403
		}
404
405
		// All was ok
406
		return true;
407
	}
408
409
	public function setRecurrence($tz, $recur): void {
410
		// only reset timezone if specified
411
		if ($tz) {
412
			$this->tz = $tz;
413
		}
414
415
		$this->recur = $recur;
416
417
		if (!isset($this->recur["changed_occurrences"])) {
418
			$this->recur["changed_occurrences"] = [];
419
		}
420
421
		if (!isset($this->recur["deleted_occurrences"])) {
422
			$this->recur["deleted_occurrences"] = [];
423
		}
424
425
		$this->deleteAttachments();
426
		$this->saveRecurrence();
427
428
		// if client has not set the recurring_pattern then we should generate it and save it
429
		$messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
0 ignored issues
show
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

429
		$messageProps = /** @scrutinizer ignore-call */ mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
Loading history...
430
		if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
431
			$this->saveRecurrencePattern();
432
		}
433
	}
434
435
	// Returns the start or end time of the occurrence on the given base date.
436
	// This assumes that the basedate you supply is in LOCAL time
437
	public function getOccurrenceStart($basedate) {
438
		$daystart = $this->dayStartOf($basedate);
439
440
		return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
441
	}
442
443
	public function getOccurrenceEnd($basedate) {
444
		$daystart = $this->dayStartOf($basedate);
445
446
		return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
447
	}
448
449
	/**
450
	 * This function returns the next remindertime starting from $timestamp
451
	 * When no next reminder exists, false is returned.
452
	 *
453
	 * Note: Before saving this new reminder time (when snoozing), you must check for
454
	 *       yourself if this reminder time is earlier than your snooze time, else
455
	 *       use your snooze time and not this reminder time.
456
	 *
457
	 * @param mixed $timestamp
458
	 */
459
	public function getNextReminderTime($timestamp) {
460
		/**
461
		 * Get next item from now until forever, but max 1 item with reminder set
462
		 * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
463
		 * Here for getting next 10 occurrences assuming that next here we will be able to find
464
		 * nextreminder occurrence in 10 occurrences.
465
		 */
466
		$items = $this->getItems($timestamp, 0x7FF00000, 10, true);
467
468
		// Initially setting nextreminder to false so when no next reminder exists, false is returned.
469
		$nextreminder = false;
470
		/*
471
		 * Loop through all reminder which we get in items variable
472
		 * and check whether the remindertime is greater than timestamp.
473
		 * On the first occurrence of greater nextreminder break the loop
474
		 * and return the value to calling function.
475
		 */
476
		for ($i = 0, $len = count($items); $i < $len; ++$i) {
477
			$item = $items[$i];
478
			$tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
479
480
			// If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
481
			if ($tempnextreminder > $timestamp) {
482
				$nextreminder = $tempnextreminder;
483
				break;
484
			}
485
		}
486
487
		return $nextreminder;
488
	}
489
490
	/**
491
	 * Note: Static function, more like a utility function.
492
	 *
493
	 * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
494
	 * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
495
	 * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
496
	 * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
497
	 *
498
	 * @param $store resource The store in which the calendar resides
499
	 * @param $calendar resource The calendar to get the items from
500
	 * @param $viewstart int Timestamp of beginning of view window
501
	 * @param $viewend int Timestamp of end of view window
502
	 * @param $propsrequested array Array of properties to return
503
	 * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
504
	 *                    expanded so that it seems that there are only many single appointments in the table.
505
	 *
506
	 * @psalm-param list{0: mixed, 1: mixed, 2?: mixed} $propsrequested
507
	 */
508
	public static function getCalendarItems($store, $calendar, $viewstart, $viewend, array $propsrequested) {
509
		return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
510
	}
511
512
	/*
513
	 * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY
514
	 *****************************************************************************************************************
515
	 */
516
517
	/**
518
	 * Generates and stores recurrence pattern string to recurring_pattern property.
519
	 */
520
	public function saveRecurrencePattern(): void {
521
		// Start formatting the properties in such a way we can apply
522
		// them directly into the recurrence pattern.
523
		$type = $this->recur['type'];
524
		$everyn = $this->recur['everyn'];
525
		$start = $this->recur['start'];
526
		$end = $this->recur['end'];
527
		$term = $this->recur['term'];
528
		$numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : 0;
529
		$startocc = $this->recur['startocc'];
530
		$endocc = $this->recur['endocc'];
531
		$pattern = '';
532
		$occSingleDayRank = false;
533
		$occTimeRange = ($startocc != 0 && $endocc != 0);
534
535
		switch ($type) {
536
			// Daily
537
			case 0x0A:
538
				if ($everyn == 1) {
539
					$type = dgettext('zarafa', 'workday');
540
					$occSingleDayRank = true;
541
				}
542
				elseif ($everyn == (24 * 60)) {
543
					$type = dgettext('zarafa', 'day');
544
					$occSingleDayRank = true;
545
				}
546
				else {
547
					$everyn /= (24 * 60);
548
					$type = dgettext('zarafa', 'days');
549
					$occSingleDayRank = false;
550
				}
551
				break;
552
				// Weekly
553
			case 0x0B:
554
				if ($everyn == 1) {
555
					$type = dgettext('zarafa', 'week');
556
					$occSingleDayRank = true;
557
				}
558
				else {
559
					$type = dgettext('zarafa', 'weeks');
560
					$occSingleDayRank = false;
561
				}
562
				break;
563
				// Monthly
564
			case 0x0C:
565
				if ($everyn == 1) {
566
					$type = dgettext('zarafa', 'month');
567
					$occSingleDayRank = true;
568
				}
569
				else {
570
					$type = dgettext('zarafa', 'months');
571
					$occSingleDayRank = false;
572
				}
573
				break;
574
				// Yearly
575
			case 0x0D:
576
				if ($everyn <= 12) {
577
					$everyn = 1;
578
					$type = dgettext('zarafa', 'year');
579
					$occSingleDayRank = true;
580
				}
581
				else {
582
					$everyn = $everyn / 12;
583
					$type = dgettext('zarafa', 'years');
584
					$occSingleDayRank = false;
585
				}
586
				break;
587
		}
588
589
		// get timings of the first occurrence
590
		$firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
591
		$firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
592
593
		$start = gmdate(dgettext('zarafa', 'd-m-Y'), $firstoccstartdate);
594
		$end = gmdate(dgettext('zarafa', 'd-m-Y'), $firstoccenddate);
595
		$startocc = gmdate(dgettext('zarafa', 'G:i'), $firstoccstartdate);
596
		$endocc = gmdate(dgettext('zarafa', 'G:i'), $firstoccenddate);
597
598
		// Based on the properties, we need to generate the recurrence pattern string.
599
		// This is obviously very easy since we can simply concatenate a bunch of strings,
600
		// however this messes up translations for languages which order their words
601
		// differently.
602
		// To improve translation quality we create a series of default strings, in which
603
		// we only have to fill in the correct variables. The base string is thus selected
604
		// based on the available properties.
605
		if ($term == 0x23) {
606
			// Never ends
607
			if ($occTimeRange) {
608
				if ($occSingleDayRank) {
609
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
610
				}
611
				else {
612
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
613
				}
614
			}
615
			else {
616
				if ($occSingleDayRank) {
617
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s.'), $type, $start);
618
				}
619
				else {
620
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s.'), $everyn, $type, $start);
621
				}
622
			}
623
		}
624
		elseif ($term == 0x22) {
625
			// After a number of times
626
			if ($occTimeRange) {
627
				if ($occSingleDayRank) {
628
					$pattern = sprintf(dngettext(
629
						'zarafa',
630
						'Occurs every %s effective %s for %s occurrence from %s to %s.',
631
						'Occurs every %s effective %s for %s occurrences from %s to %s.',
632
						$numocc
633
					), $type, $start, $numocc, $startocc, $endocc);
634
				}
635
				else {
636
					$pattern = sprintf(dngettext(
637
						'zarafa',
638
						'Occurs every %s %s effective %s for %s occurrence from %s to %s.',
639
						'Occurs every %s %s effective %s for %s occurrences %s to %s.',
640
						$numocc
641
					), $everyn, $type, $start, $numocc, $startocc, $endocc);
642
				}
643
			}
644
			else {
645
				if ($occSingleDayRank) {
646
					$pattern = sprintf(dngettext(
647
						'zarafa',
648
						'Occurs every %s effective %s for %s occurrence.',
649
						'Occurs every %s effective %s for %s occurrences.',
650
						$numocc
651
					), $type, $start, $numocc);
652
				}
653
				else {
654
					$pattern = sprintf(dngettext(
655
						'zarafa',
656
						'Occurs every %s %s effective %s for %s occurrence.',
657
						'Occurs every %s %s effective %s for %s occurrences.',
658
						$numocc
659
					), $everyn, $type, $start, $numocc);
660
				}
661
			}
662
		}
663
		elseif ($term == 0x21) {
664
			// After the given enddate
665
			if ($occTimeRange) {
666
				if ($occSingleDayRank) {
667
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
668
				}
669
				else {
670
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
671
				}
672
			}
673
			else {
674
				if ($occSingleDayRank) {
675
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s until %s.'), $type, $start, $end);
676
				}
677
				else {
678
					$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
679
				}
680
			}
681
		}
682
683
		if (!empty($pattern)) {
684
			mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
0 ignored issues
show
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

684
			/** @scrutinizer ignore-call */ 
685
   mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
Loading history...
685
		}
686
	}
687
688
	/*
689
	 * Remove an exception by base_date. This is the base date in local daystart time
690
	 */
691
	/**
692
	 * @param false|int $base_date
693
	 */
694
	public function deleteException($base_date): void {
695
		// Remove all exceptions on $base_date from the deleted and changed occurrences lists
696
697
		// Remove all items in $todelete from deleted_occurrences
698
		$new = [];
699
700
		foreach ($this->recur["deleted_occurrences"] as $entry) {
701
			if ($entry != $base_date) {
702
				$new[] = $entry;
703
			}
704
		}
705
		$this->recur["deleted_occurrences"] = $new;
706
707
		$new = [];
708
709
		foreach ($this->recur["changed_occurrences"] as $entry) {
710
			if (!$this->isSameDay($entry["basedate"], $base_date)) {
711
				$new[] = $entry;
712
			}
713
			else {
714
				$this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
715
			}
716
		}
717
718
		$this->recur["changed_occurrences"] = $new;
719
	}
720
721
	/**
722
	 * Function which saves the exception data in an attachment.
723
	 *
724
	 * @param array        $exception_props  the exception data (like any other MAPI appointment)
725
	 * @param array        $exception_recips list of recipients
726
	 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
0 ignored issues
show
The type mapi_message was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
727
	 */
728
	public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false): void {
729
		// Create new attachment.
730
		$attachment = mapi_message_createattach($this->message);
0 ignored issues
show
The function mapi_message_createattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

730
		$attachment = /** @scrutinizer ignore-call */ mapi_message_createattach($this->message);
Loading history...
731
		$props = [];
732
		$props[PR_ATTACHMENT_FLAGS] = 2;
0 ignored issues
show
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
733
		$props[PR_ATTACHMENT_HIDDEN] = true;
0 ignored issues
show
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
734
		$props[PR_ATTACHMENT_LINKID] = 0;
0 ignored issues
show
The constant PR_ATTACHMENT_LINKID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
735
		$props[PR_ATTACH_FLAGS] = 0;
0 ignored issues
show
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
736
		$props[PR_ATTACH_METHOD] = ATTACH_EMBEDDED_MSG;
0 ignored issues
show
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
737
		$props[PR_DISPLAY_NAME] = "Exception";
0 ignored issues
show
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
738
		$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
0 ignored issues
show
The constant PR_EXCEPTION_STARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
739
		$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
0 ignored issues
show
The constant PR_EXCEPTION_ENDTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
740
		mapi_setprops($attachment, $props);
0 ignored issues
show
The function mapi_setprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

740
		/** @scrutinizer ignore-call */ 
741
  mapi_setprops($attachment, $props);
Loading history...
741
742
		$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

742
		$imessage = /** @scrutinizer ignore-call */ mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
Loading history...
743
744
		if ($copy_attach_from) {
745
			$attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
0 ignored issues
show
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

745
			$attachmentTable = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($copy_attach_from);
Loading history...
746
			if ($attachmentTable) {
747
				$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
0 ignored issues
show
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

747
				$attachments = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
Loading history...
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
748
749
				foreach ($attachments as $attach_props) {
750
					$attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_openattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

750
					$attach_old = /** @scrutinizer ignore-call */ mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
Loading history...
751
					$attach_newResourceMsg = mapi_message_createattach($imessage);
752
					mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
0 ignored issues
show
The function mapi_copyto was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

752
					/** @scrutinizer ignore-call */ 
753
     mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
Loading history...
753
					mapi_savechanges($attach_newResourceMsg);
0 ignored issues
show
The function mapi_savechanges was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

753
					/** @scrutinizer ignore-call */ 
754
     mapi_savechanges($attach_newResourceMsg);
Loading history...
754
				}
755
			}
756
		}
757
758
		$props = $props + $exception_props;
759
760
		// FIXME: the following piece of code is written to fix the creation
761
		// of an exception. This is only a quickfix as it is not yet possible
762
		// to change an existing exception.
763
		// remove mv properties when needed
764
		foreach ($props as $propTag => $propVal) {
765
			if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) {
0 ignored issues
show
The function mapi_prop_type was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

765
			if ((/** @scrutinizer ignore-call */ mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) {
Loading history...
766
				unset($props[$propTag]);
767
			}
768
		}
769
770
		mapi_setprops($imessage, $props);
771
772
		$this->setExceptionRecipients($imessage, $exception_recips, true);
773
774
		mapi_savechanges($imessage);
775
		mapi_savechanges($attachment);
776
	}
777
778
	/**
779
	 * Function which deletes the attachment of an exception.
780
	 *
781
	 * @param mixed $base_date base date of the attachment. Should be in GMT. The attachment
782
	 *                         actually saves the real time of the original date, so we have
783
	 *                         to check whether it's on the same day.
784
	 */
785
	public function deleteExceptionAttachment($base_date): void {
786
		$attachments = mapi_message_getattachmenttable($this->message);
0 ignored issues
show
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

786
		$attachments = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($this->message);
Loading history...
787
		// Retrieve only exceptions which are stored as embedded messages
788
		$attach_res = [
789
			RES_PROPERTY,
790
			[
791
				RELOP => RELOP_EQ,
792
				ULPROPTAG => PR_ATTACH_METHOD,
0 ignored issues
show
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
793
				VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
794
			],
795
		];
796
		$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
0 ignored issues
show
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

796
		$attachRows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
Loading history...
797
798
		foreach ($attachRows as $attachRow) {
799
			$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_openattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

799
			$tempattach = /** @scrutinizer ignore-call */ mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
Loading history...
800
			$exception = mapi_attach_openobj($tempattach);
0 ignored issues
show
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

800
			$exception = /** @scrutinizer ignore-call */ mapi_attach_openobj($tempattach);
Loading history...
801
802
			$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
0 ignored issues
show
The function mapi_message_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

802
			$data = /** @scrutinizer ignore-call */ mapi_message_getprops($exception, [$this->proptags["basedate"]]);
Loading history...
803
804
			if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
805
				mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_deleteattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

805
				/** @scrutinizer ignore-call */ 
806
    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
Loading history...
806
			}
807
		}
808
	}
809
810
	/**
811
	 * Function which deletes all attachments of a message.
812
	 */
813
	public function deleteAttachments(): void {
814
		$attachments = mapi_message_getattachmenttable($this->message);
0 ignored issues
show
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

814
		$attachments = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($this->message);
Loading history...
815
		$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
0 ignored issues
show
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

815
		$attachTable = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
Loading history...
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
816
817
		foreach ($attachTable as $attachRow) {
818
			if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
819
				mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_deleteattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

819
				/** @scrutinizer ignore-call */ 
820
    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
Loading history...
820
			}
821
		}
822
	}
823
824
	/**
825
	 * Get an exception attachment based on its basedate.
826
	 *
827
	 * @param mixed $base_date
828
	 */
829
	public function getExceptionAttachment($base_date) {
830
		// Retrieve only exceptions which are stored as embedded messages
831
		$attach_res = [
832
			RES_PROPERTY,
833
			[
834
				RELOP => RELOP_EQ,
835
				ULPROPTAG => PR_ATTACH_METHOD,
0 ignored issues
show
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
836
				VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
837
			],
838
		];
839
		$attachments = mapi_message_getattachmenttable($this->message);
0 ignored issues
show
The function mapi_message_getattachmenttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

839
		$attachments = /** @scrutinizer ignore-call */ mapi_message_getattachmenttable($this->message);
Loading history...
840
		$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
0 ignored issues
show
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

840
		$attachRows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
Loading history...
841
842
		if (is_array($attachRows)) {
843
			foreach ($attachRows as $attachRow) {
844
				$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
0 ignored issues
show
The function mapi_message_openattach was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

844
				$tempattach = /** @scrutinizer ignore-call */ mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
Loading history...
845
				$exception = mapi_attach_openobj($tempattach);
0 ignored issues
show
The function mapi_attach_openobj was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

845
				$exception = /** @scrutinizer ignore-call */ mapi_attach_openobj($tempattach);
Loading history...
846
847
				$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
0 ignored issues
show
The function mapi_message_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

847
				$data = /** @scrutinizer ignore-call */ mapi_message_getprops($exception, [$this->proptags["basedate"]]);
Loading history...
848
849
				if (!isset($data[$this->proptags["basedate"]])) {
850
					// if no basedate found then it could be embedded message so ignore it
851
					// we need proper restriction to exclude embedded messages as well
852
					continue;
853
				}
854
855
				if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
856
					return $tempattach;
857
				}
858
			}
859
		}
860
861
		return false;
862
	}
863
864
	/**
865
	 * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
866
	 * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
867
	 * - The occurrence isn't specified as a deleted occurrence.
868
	 *
869
	 * @param array $items        reference to the array to be added to
870
	 * @param int   $start        start of timeframe in GMT TIME
871
	 * @param int   $end          end of timeframe in GMT TIME
872
	 * @param int   $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
873
	 * @param int   $startocc     start of occurrence since beginning of day in minutes
874
	 * @param int   $endocc       end of occurrence since beginning of day in minutes
875
	 * @param mixed $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
876
	 * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
877
	 *
878
	 * @return null|false
879
	 */
880
	public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
881
		$exception = $this->isException($basedate);
882
		if ($exception) {
883
			return false;
884
		}
885
		$occstart = $basedate + $startocc * 60;
886
		$occend = $basedate + $endocc * 60;
887
888
		// Convert to GMT
889
		$occstart = $this->toGMT($tz, $occstart);
890
		$occend = $this->toGMT($tz, $occend);
891
892
		/**
893
		 * FIRST PART: Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
894
		 * see any part of the appointment. Partial overlaps DO match.
895
		 *
896
		 * SECOND PART: check if occurrence is not a zero duration occurrence which
897
		 * starts at 00:00 and ends on 00:00. if it is so, then process
898
		 * the occurrence and send it in response.
899
		 */
900
		if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
901
			return;
902
		}
903
904
		// Properties for this occurrence are the same as the main object,
905
		// With these properties overridden
906
		$newitem = $this->messageprops;
907
		$newitem[$this->proptags["startdate"]] = $occstart;
908
		$newitem[$this->proptags["duedate"]] = $occend;
909
		$newitem[$this->proptags["commonstart"]] = $occstart;
910
		$newitem[$this->proptags["commonend"]] = $occend;
911
		$newitem["basedate"] = $basedate;
912
913
		// If reminderonly is set, only add reminders
914
		if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
915
			return;
916
		}
917
918
		$items[] = $newitem;
919
	}
920
921
	/**
922
	 * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
923
	 *
924
	 * @param array $items reference to the array to be added to
925
	 * @param date  $start start of timeframe in GMT TIME
926
	 * @param date  $end   end of timeframe in GMT TIME
927
	 */
928
	public function processExceptionItems(&$items, $start, $end): void {
929
		$limit = 0;
930
		foreach ($this->recur["changed_occurrences"] as $exception) {
931
			// Convert to GMT
932
			$occstart = $this->toGMT($this->tz, $exception["start"]);
933
			$occend = $this->toGMT($this->tz, $exception["end"]);
934
935
			// Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
936
			// see any part of the appointment. Partial overlaps DO match.
937
			if ($occstart >= $end || $occend <= $start) {
938
				continue;
939
			}
940
941
			array_push($items, $this->getExceptionProperties($exception));
942
			if (count($items) == $limit) {
943
				break;
944
			}
945
		}
946
	}
947
948
	/**
949
	 * Function which verifies if on the given date an exception, delete or change, occurs.
950
	 *
951
	 * @param mixed $basedate
952
	 *
953
	 * @return bool true - if an exception occurs on the given date, false - no exception occurs on the given date
954
	 */
955
	public function isException($basedate) {
956
		if ($this->isDeleteException($basedate)) {
957
			return true;
958
		}
959
960
		if ($this->getChangeException($basedate) != false) {
961
			return true;
962
		}
963
964
		return false;
965
	}
966
967
	/**
968
	 * Returns TRUE if there is a DELETE exception on the given base date.
969
	 *
970
	 * @param mixed $basedate
971
	 */
972
	public function isDeleteException($basedate): bool {
973
		// Check if the occurrence is deleted on the specified date
974
		foreach ($this->recur["deleted_occurrences"] as $deleted) {
975
			if ($this->isSameDay($deleted, $basedate)) {
976
				return true;
977
			}
978
		}
979
980
		return false;
981
	}
982
983
	/**
984
	 * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
985
	 *
986
	 * @param mixed $basedate
987
	 */
988
	public function getChangeException($basedate) {
989
		// Check if the occurrence is modified on the specified date
990
		foreach ($this->recur["changed_occurrences"] as $changed) {
991
			if ($this->isSameDay($changed["basedate"], $basedate)) {
992
				return $changed;
993
			}
994
		}
995
996
		return false;
997
	}
998
999
	/**
1000
	 * Function to see if two dates are on the same day.
1001
	 *
1002
	 * @param date  $time1 date 1
1003
	 * @param date  $time2 date 2
1004
	 * @param mixed $date1
1005
	 * @param mixed $date2
1006
	 *
1007
	 * @return bool Returns TRUE when both dates are on the same day
1008
	 */
1009
	public function isSameDay($date1, $date2) {
1010
		$time1 = $this->gmtime($date1);
1011
		$time2 = $this->gmtime($date2);
1012
1013
		return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
1014
	}
1015
1016
	/**
1017
	 * Function which sets recipients for an exception.
1018
	 *
1019
	 * The $exception_recips can be provided in 2 ways:
1020
	 *  - A delta which indicates which recipients must be added, removed or deleted.
1021
	 *  - A complete array of the recipients which should be applied to the message.
1022
	 *
1023
	 * The first option is preferred as it will require less work to be executed.
1024
	 *
1025
	 * @param resource $message          exception attachment of recurring item
1026
	 * @param array    $exception_recips list of recipients
1027
	 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1028
	 *                                   message to the attachment by default. False if only the $exception_recips changes should
1029
	 *                                   be applied.
1030
	 */
1031
	public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true): void {
1032
		if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1033
			$this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1034
		}
1035
		else {
1036
			$this->setAllExceptionRecipients($message, $exception_recips);
1037
		}
1038
	}
1039
1040
	/**
1041
	 * Function which applies the provided delta for recipients changes to the exception.
1042
	 *
1043
	 * The $exception_recips should be an array containing the following keys:
1044
	 *  - "add": this contains an array of recipients which must be added
1045
	 *  - "remove": This contains an array of recipients which must be removed
1046
	 *  - "modify": This contains an array of recipients which must be modified
1047
	 *
1048
	 * @param resource $message          exception attachment of recurring item
1049
	 * @param array    $exception_recips list of recipients
1050
	 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1051
	 *                                   message to the attachment by default. False if only the $exception_recips changes should
1052
	 *                                   be applied.
1053
	 * @param mixed    $exception
1054
	 */
1055
	public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips): void {
1056
		// Check if the recipients from the original message should be copied,
1057
		// if so, open the recipient table of the parent message and apply all
1058
		// rows on the target recipient.
1059
		if ($copy_orig_recips === true) {
1060
			$origTable = mapi_message_getrecipienttable($this->message);
0 ignored issues
show
The function mapi_message_getrecipienttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1060
			$origTable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($this->message);
Loading history...
1061
			$recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
0 ignored issues
show
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1061
			$recipientRows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($origTable, $this->recipprops);
Loading history...
1062
			mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
0 ignored issues
show
The function mapi_message_modifyrecipients was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1062
			/** @scrutinizer ignore-call */ 
1063
   mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
Loading history...
1063
		}
1064
1065
		// Add organizer to meeting only if it is not organized.
1066
		$msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
0 ignored issues
show
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1066
		$msgprops = /** @scrutinizer ignore-call */ mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
Loading history...
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1067
		if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1068
			$this->addOrganizer($msgprops, $exception_recips['add']);
1069
		}
1070
1071
		// Remove all deleted recipients
1072
		if (isset($exception_recips['remove'])) {
1073
			foreach ($exception_recips['remove'] as &$recip) {
1074
				if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
0 ignored issues
show
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1075
					$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1076
				}
1077
				else {
1078
					$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1079
				}
1080
				$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;		// No Response required
0 ignored issues
show
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1081
			}
1082
			unset($recip);
1083
			mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1084
		}
1085
1086
		// Add all new recipients
1087
		if (isset($exception_recips['add'])) {
1088
			mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1089
		}
1090
1091
		// Modify the existing recipients
1092
		if (isset($exception_recips['modify'])) {
1093
			mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1094
		}
1095
	}
1096
1097
	/**
1098
	 * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1099
	 *
1100
	 * The $exception_recips should be an array containing all recipients which must be applied
1101
	 * to the exception. This will copy all recipients from the original message and then start filter
1102
	 * out all recipients which are not provided by the $exception_recips list.
1103
	 *
1104
	 * @param resource $message          exception attachment of recurring item
1105
	 * @param array    $exception_recips list of recipients
1106
	 */
1107
	public function setAllExceptionRecipients($message, $exception_recips): void {
1108
		$deletedRecipients = [];
1109
		$useMessageRecipients = false;
1110
1111
		$recipientTable = mapi_message_getrecipienttable($message);
0 ignored issues
show
The function mapi_message_getrecipienttable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1111
		$recipientTable = /** @scrutinizer ignore-call */ mapi_message_getrecipienttable($message);
Loading history...
1112
		$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
0 ignored issues
show
The function mapi_table_queryallrows was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1112
		$recipientRows = /** @scrutinizer ignore-call */ mapi_table_queryallrows($recipientTable, $this->recipprops);
Loading history...
1113
1114
		if (empty($recipientRows)) {
1115
			$useMessageRecipients = true;
1116
			$recipientTable = mapi_message_getrecipienttable($this->message);
1117
			$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1118
		}
1119
1120
		// Add organizer to meeting only if it is not organized.
1121
		$msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
0 ignored issues
show
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The function mapi_getprops was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1121
		$msgprops = /** @scrutinizer ignore-call */ mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
Loading history...
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1122
		if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1123
			$this->addOrganizer($msgprops, $exception_recips);
1124
		}
1125
1126
		if (!empty($exception_recips)) {
1127
			foreach ($recipientRows as $key => $recipient) {
1128
				$found = false;
1129
				foreach ($exception_recips as $excep_recip) {
1130
					if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
0 ignored issues
show
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1131
						$found = true;
1132
					}
1133
				}
1134
1135
				if (!$found) {
1136
					$foundInDeletedRecipients = false;
1137
					// Look if the $recipient is in the list of deleted recipients
1138
					if (!empty($deletedRecipients)) {
1139
						foreach ($deletedRecipients as $recip) {
1140
							if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1141
								$foundInDeletedRecipients = true;
1142
								break;
1143
							}
1144
						}
1145
					}
1146
1147
					// If recipient is not in list of deleted recipient, add him
1148
					if (!$foundInDeletedRecipients) {
1149
						if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
0 ignored issues
show
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1150
							$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1151
						}
1152
						else {
1153
							$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1154
						}
1155
						$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;	// No Response required
0 ignored issues
show
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1156
						$deletedRecipients[] = $recipient;
1157
					}
1158
				}
1159
1160
				// When $message contains a non-empty recipienttable, we must delete the recipients
1161
				// before re-adding them. However, when $message is doesn't contain any recipients,
1162
				// we are using the recipient table of the original message ($this->message)
1163
				// rather then $message. In that case, we don't need to remove the recipients
1164
				// from the $message, as the recipient table is already empty, and
1165
				// mapi_message_modifyrecipients() will throw an error.
1166
				if ($useMessageRecipients === false) {
1167
					mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
0 ignored issues
show
The function mapi_message_modifyrecipients was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1167
					/** @scrutinizer ignore-call */ 
1168
     mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
Loading history...
1168
				}
1169
			}
1170
			$exception_recips = array_merge($exception_recips, $deletedRecipients);
1171
		}
1172
		else {
1173
			$exception_recips = $recipientRows;
1174
		}
1175
1176
		if (!empty($exception_recips)) {
1177
			// Set the new list of recipients on the exception message, this also removes the existing recipients
1178
			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $exception_recips);
1179
		}
1180
	}
1181
1182
	/**
1183
	 * Function returns basedates of all changed occurrences.
1184
	 *
1185
	 * @return array|false array( 0 => 123459321 )
1186
	 *
1187
	 * @psalm-return false|list<mixed>
1188
	 */
1189
	public function getAllExceptions() {
1190
		if (!empty($this->recur["changed_occurrences"])) {
1191
			$result = [];
1192
			foreach ($this->recur["changed_occurrences"] as $exception) {
1193
				$result[] = $exception["basedate"];
1194
			}
1195
1196
			return $result;
1197
		}
1198
1199
		return false;
1200
	}
1201
1202
	/**
1203
	 * Function which adds organizer to recipient list which is passed.
1204
	 * This function also checks if it has organizer.
1205
	 *
1206
	 * @param array $messageProps message properties
1207
	 * @param array $recipients   recipients list of message
1208
	 * @param bool  $isException  true if we are processing recipient of exception
1209
	 */
1210
	public function addOrganizer($messageProps, &$recipients, $isException = false): void {
1211
		$hasOrganizer = false;
1212
		// Check if meeting already has an organizer.
1213
		foreach ($recipients as $key => $recipient) {
1214
			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
0 ignored issues
show
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1215
				$hasOrganizer = true;
1216
			}
1217
			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1218
				// Recipients for an occurrence
1219
				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1220
			}
1221
		}
1222
1223
		if (!$hasOrganizer) {
1224
			// Create organizer.
1225
			$organizer = [];
1226
			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
0 ignored issues
show
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1227
			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1228
			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1229
			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
0 ignored issues
show
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1230
			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1231
			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1232
			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
0 ignored issues
show
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1233
			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1234
			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1235
1236
			// Add organizer to recipients list.
1237
			array_unshift($recipients, $organizer);
1238
		}
1239
	}
1240
}
1241
1242
/*
1243
1244
From http://www.ohelp-one.com/new-6765483-3268.html:
1245
1246
Recurrence Data Structure Offset Type Value
1247
1248
0 ULONG (?) Constant : { 0x04, 0x30, 0x04, 0x30}
1249
1250
4 UCHAR 0x0A + recurrence type: 0x0A for daily, 0x0B for weekly, 0x0C for
1251
monthly, 0x0D for yearly
1252
1253
5 UCHAR Constant: { 0x20}
1254
1255
6 ULONG Seems to be a variant of the recurrence type: 1 for daily every n
1256
days, 2 for daily every weekday and weekly, 3 for monthly or yearly. The
1257
special exception is regenerating tasks that regenerate on a weekly basis: 0
1258
is used in that case (I have no idea why).
1259
1260
Here's the recurrence-type-specific data. Because the daily every N days
1261
data are 4 bytes shorter than the data for the other types, the offsets for
1262
the rest of the data will be 4 bytes off depending on the recurrence type.
1263
1264
Daily every N days:
1265
1266
10 ULONG ( N - 1) * ( 24 * 60). I'm not sure what this is used for, but it's consistent.
1267
1268
14 ULONG N * 24 * 60: minutes between recurrences
1269
1270
18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1271
regenerating tasks.
1272
1273
Daily every weekday (this is essentially a subtype of weekly recurrence):
1274
1275
10 ULONG 6 * 24 * 60: minutes between recurrences ( a week... sort of)
1276
1277
14 ULONG 1: recur every week (corresponds to the second parameter for weekly
1278
recurrence)
1279
1280
18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1281
regenerating tasks.
1282
1283
22 ULONG 0x3E: bitmask for recurring every weekday (corresponds to fourth
1284
parameter for weekly recurrence)
1285
1286
Weekly every N weeks for all events and non-regenerating tasks:
1287
1288
10 ULONG 6 * 24 * 60: minutes between recurrences (a week... sort of)
1289
1290
14 ULONG N: recurrence interval
1291
1292
18 ULONG Constant: 0
1293
1294
22 ULONG Bitmask for determining which days of the week the event recurs on
1295
( 1 << dayOfWeek, where Sunday is 0).
1296
1297
Weekly every N weeks for regenerating tasks: 10 ULONG Constant: 0
1298
1299
14 ULONG N * 7 * 24 * 60: recurrence interval in minutes between occurrences
1300
1301
18 ULONG Constant: 1
1302
1303
Monthly every N months on day D:
1304
1305
10 ULONG This is the most complicated value
1306
in the entire mess. It's basically a very complicated way of stating the
1307
recurrence interval. I tweaked fbs' basic algorithm. DateTime::MonthInDays
1308
simply returns the number of days in a given month, e.g. 31 for July for 28
1309
for February (the algorithm doesn't take into account leap years, but it
1310
doesn't seem to matter). My DateTime object, like Microsoft's COleDateTime,
1311
uses 1-based months (i.e. January is 1, not 0). With that in mind, this
1312
works:
1313
1314
long monthIndex = ( ( ( ( 12 % schedule-=GetInterval()) *
1315
1316
( ( schedule-=GetStartDate().GetYear() - 1601) %
1317
1318
schedule-=GetInterval())) % schedule-=GetInterval()) +
1319
1320
( schedule-=GetStartDate().GetMonth() - 1)) % schedule-=GetInterval();
1321
1322
for( int i = 0; i < monthIndex; i++)
1323
1324
{
1325
1326
value += DateTime::GetDaysInMonth( ( i % 12) + 1) * 24 * 60;
1327
1328
}
1329
1330
This should work for any recurrence interval, including those greater than
1331
12.
1332
1333
14 ULONG N: recurrence interval
1334
1335
18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1336
regenerating tasks.
1337
1338
22 ULONG D: day of month the event recurs on (if this value is greater than
1339
the number of days in a given month [e.g. 31 for and recurs in June], then
1340
the event will recur on the last day of the month)
1341
1342
Monthly every N months on the Xth Y (e.g. "2nd Tuesday"):
1343
1344
10 ULONG See above: same as for monthly every N months on day D
1345
1346
14 ULONG N: recurrence interval
1347
1348
18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1349
regenerating tasks.
1350
1351
22 ULONG Y: bitmask for determining which day of the week the event recurs
1352
on (see weekly every N weeks). Some useful values are 0x7F for any day, 0x3E
1353
for a weekday, or 0x41 for a weekend day.
1354
1355
26 ULONG X: 1 for first occurrence, 2 for second, etc. 5 for last
1356
occurrence. E.g. for "2nd Tuesday", you should have values of 0x04 for the
1357
prior value and 2 for this one.
1358
1359
Yearly on day D of month M:
1360
1361
10 ULONG M (sort of): This is another messy
1362
value. It's the number of minute since the startning of the year to the
1363
given month. For an explanation of GetDaysInMonth, see monthly every N
1364
months. This will work:
1365
1366
ULONG monthOfYearInMinutes = 0;
1367
1368
for( int i = DateTime::cJanuary; i < schedule-=GetMonth(); i++)
1369
1370
{
1371
1372
monthOfYearInMinutes += DateTime::GetDaysInMonth( i) * 24 * 60;
1373
1374
}
1375
1376
1377
1378
14 ULONG 12: recurrence interval in months. Naturally, 12.
1379
1380
18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1381
regenerating tasks.
1382
1383
22 ULONG D: day of month the event recurs on. See monthly every N months on
1384
day D.
1385
1386
Yearly on the Xth Y of month M: 10 ULONG M (sort of): See yearly on day D of
1387
month M.
1388
1389
14 ULONG 12: recurrence interval in months. Naturally, 12.
1390
1391
18 ULONG Constant: 0
1392
1393
22 ULONG Y: see monthly every N months on the Xth Y.
1394
1395
26 ULONG X: see monthly every N months on the Xth Y.
1396
1397
After these recurrence-type-specific values, the offsets will change
1398
depending on the type. For every type except daily every N days, the offsets
1399
will grow by at least 4. For those types using the Xth Y, the offsets will
1400
grow by an additional 4, for a total of 8. The offsets for the rest of these
1401
values will be given for the most basic case, daily every N days, i.e.
1402
without any growth. Adjust as necessary. Also, the presence of exceptions
1403
will change the offsets following the exception data by a variable number of
1404
bytes, so the offsets given in the table are accurate only for those
1405
recurrence patterns without any exceptions.
1406
1407
1408
22 UCHAR Type of pattern termination: 0x21 for terminating on a given date, 0x22 for terminating
1409
after a given number of recurrences, or 0x23 for never terminating
1410
(recurring infinitely)
1411
1412
23 UCHARx3 Constant: { 0x20, 0x00, 0x00}
1413
1414
26 ULONG Number of occurrences in pattern: 0 for infinite recurrence,
1415
otherwise supply the value, even if it terminates on a given date, not after
1416
a given number
1417
1418
30 ULONG Constant: 0
1419
1420
34 ULONG Number of exceptions to pattern (i.e. deleted or changed
1421
occurrences)
1422
1423
.... ULONGxN Base date of each exception, given in hundreds of nanoseconds
1424
since 1601, so see below to turn them into a comprehensible format. The base
1425
date of an exception is the date (and only the date-- not the time) the
1426
exception would have occurred on in the pattern. They must occur in
1427
ascending order.
1428
1429
38 ULONG Number of changed exceptions (i.e. total number of exceptions -
1430
number of deleted exceptions): if there are changed exceptions, again, more
1431
data will be needed, but that will wait
1432
1433
.... ULONGxN Start date (and only the date-- not the time) of each changed
1434
exception, i.e. the exceptions which aren't deleted. These must also occur
1435
in ascending order. If all of the exceptions are deleted, this data will be
1436
absent. If present, they will be in the format above. Any dates that are in
1437
the first list but not in the second are exceptions that have been deleted
1438
(i.e. the difference between the two sets). Note that this is the start date
1439
(including time), not the base date. Given that the values are unordered and
1440
that they can't be matched up against the previous list in this iteration of
1441
the recurrence data (they could in previous ones), it is very difficult to
1442
tell which exceptions are deleted and which are changed. Fortunately, for
1443
this new format, the base dates are given on the attachment representing the
1444
changed exception (described below), so you can simply ignore this list of
1445
changed exceptions. Just create a list of exceptions from the previous list
1446
and assume they're all deleted unless you encounter an attachment with a
1447
matching base date later on.
1448
1449
42 ULONG Start date of pattern given in hundreds of nanoseconds since 1601;
1450
see below for an explanation.
1451
1452
46 ULONG End date of pattern: see start date of pattern
1453
1454
50 ULONG Constant: { 0x06, 0x30, 0x00, 0x00}
1455
1456
NOTE: I find the following 8-byte sequence of bytes to be very useful for
1457
orienting myself when looking at the raw data. If you can find { 0x06, 0x30,
1458
0x00, 0x00, 0x08, 0x30, 0x00, 0x00}, you can use these tables to work either
1459
forwards or backwards to find the data you need. The sequence sort of
1460
delineates certain critical exception-related data and delineates the
1461
exceptions themselves from the rest of the data and is relatively easy to
1462
find. If you're going to be meddling in here a lot, I suggest making a
1463
friend of ol' 0x00003006.
1464
1465
54 UCHAR This number is some kind of version indicator. Use 0x08 for Outlook
1466
2003. I believe 0x06 is Outlook 2000 and possibly 98, while 0x07 is Outlook
1467
XP. This number must be consistent with the features of the data structure
1468
generated by the version of Outlook indicated thereby-- there are subtle
1469
differences between the structures, and, if the version doesn't match the
1470
data, Outlook will sometimes failto read the structure.
1471
1472
55 UCHARx3 Constant: { 0x30, 0x00, 0x00}
1473
1474
58 ULONG Start time of occurrence in minutes: e.g. 0 for midnight or 720 for
1475
12 PM
1476
1477
62 ULONG End time of occurrence in minutes: i.e. start time + duration, e.g.
1478
900 for an event that starts at 12 PM and ends at 3PM
1479
1480
Exception Data 66 USHORT Number of changed exceptions: essentially a check
1481
on the prior occurrence of this value; should be equivalent.
1482
1483
NOTE: The following structure will occur N many times (where N = number of
1484
changed exceptions), and each structure can be of variable length.
1485
1486
.... ULONG Start date of changed exception given in hundreds of nanoseconds
1487
since 1601
1488
1489
.... ULONG End date of changed exception given in hundreds of nanoseconds
1490
since 1601
1491
1492
.... ULONG This is a value I don't clearly understand. It seems to be some
1493
kind of archival value that matches the start time most of the time, but
1494
will lag behind when the start time is changed and then match up again under
1495
certain conditions later. In any case, setting to the same value as the
1496
start time seems to work just fine (more information on this value would be
1497
appreciated).
1498
1499
.... USHORT Bitmask of changes to the exception (see below). This will be 0
1500
if the only changes to the exception were to its start or end time.
1501
1502
.... ULONGxN Numeric values (e.g. label or minutes to remind before the
1503
event) changed in the exception. These will occur in the order of their
1504
corresponding bits (see below). If no numeric values were changed, then
1505
these values will be absent.
1506
1507
NOTE: The following three values constitute a single sub-structure that will
1508
occur N many times, where N is the number of strings that are changed in the
1509
exception. Since there are at most 2 string values that can be excepted
1510
(i.e. subject [or description], and location), there can at most be two of
1511
these, but there may be none.
1512
1513
.... USHORT Length of changed string value with NULL character
1514
1515
.... USHORT Length of changed string value without NULL character (i.e.
1516
previous value - 1)
1517
1518
.... CHARxN Changed string value (without NULL terminator)
1519
1520
Unicode Data NOTE: If a string value was changed on an exception, those
1521
changed string values will reappear here in Unicode format after 8 bytes of
1522
NULL padding (possibly a Unicode terminator?). For each exception with a
1523
changed string value, there will be an identifier, followed by the changed
1524
strings in Unicode. The strings will occur in the order of their
1525
corresponding bits (see below). E.g., if both subject and location were
1526
changed in the exception, there would be the 3-ULONG identifier, then the
1527
length of the subject, then the subject, then the length of the location,
1528
then the location.
1529
1530
70 ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}. This
1531
padding serves as a barrier between the older data structure and the
1532
appended Unicode data. This is the same sequence as the Unicode terminator,
1533
but I'm not sure whether that's its identity or not.
1534
1535
.... ULONGx3 These are the three times used to identify the exception above:
1536
start date, end date, and repeated start date. These should be the same as
1537
they were above.
1538
1539
.... USHORT Length of changed string value without NULL character. This is
1540
given as count of WCHARs, so it should be identical to the value above.
1541
1542
.... WCHARxN Changed string value in Unicode (without NULL terminator)
1543
1544
Terminator ... ULONGxN Constant: { 0x00, 0x00, 0x00, 0x00}. 4 bytes of NULL
1545
padding per changed exception. If there were no changed exceptions, all
1546
you'll need is the final terminator below.
1547
1548
.... ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}.
1549
1550
*/
1551