Test Failed
Push — master ( 1ac602...f9946d )
by
unknown
12:58 queued 10:17
created

Recurrence::getExceptionProperties()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 43
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 22
c 1
b 0
f 0
nop 1
dl 0
loc 43
rs 8.2111
nc 128

1 Method

Rating   Name   Duplication   Size   Complexity  
A Recurrence::setExceptionRecipients() 0 6 4
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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 resource $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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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) {
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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]);
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) {
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
introduced by
$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
Bug introduced by
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);
300
301
				// Set exception properties on embedded message and save
302
				mapi_setprops($message, $exception_props);
303
				$this->setExceptionRecipients($message, $exception_recips, false);
304
				mapi_savechanges($message);
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
Bug introduced by
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
Bug introduced by
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) {
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
		 * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
374
		 */
375
		public function isValidReminderTime($basedate, $reminderminutes, $startdate) {
376
			// get all occurrence items before the seleceted items occurrence starttime
377
			$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
0 ignored issues
show
Bug introduced by
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

377
			$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], /** @scrutinizer ignore-type */ $this->toGMT($this->tz, $basedate));
Loading history...
378
379
			if (!empty($occitems)) {
380
				// as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
381
				$previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
382
383
				// if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
384
				if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
385
					return false;
386
				}
387
			}
388
389
			// Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
390
			$currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
0 ignored issues
show
Bug introduced by
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

390
			$currentOcc = $this->getItems(/** @scrutinizer ignore-type */ $this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
Loading history...
391
392
			// If there are another two occurrences, then the first is the current occurrence, and the one after that
393
			// is the next occurrence.
394
			if (count($currentOcc) > 1) {
395
				$next = $currentOcc[1];
396
				// Get reminder time of the next occurrence.
397
				$nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
398
				// If the reminder time of the next item is before the start of this item, then that's not allowed
399
				if ($nextOccReminderTime <= $startdate) {
400
					return false;
401
				}
402
			}
403
404
			// All was ok
405
			return true;
406
		}
407
408
		public function setRecurrence($tz, $recur) {
409
			// only reset timezone if specified
410
			if ($tz) {
411
				$this->tz = $tz;
412
			}
413
414
			$this->recur = $recur;
415
416
			if (!isset($this->recur["changed_occurrences"])) {
417
				$this->recur["changed_occurrences"] = [];
418
			}
419
420
			if (!isset($this->recur["deleted_occurrences"])) {
421
				$this->recur["deleted_occurrences"] = [];
422
			}
423
424
			$this->deleteAttachments();
425
			$this->saveRecurrence();
426
427
			// if client has not set the recurring_pattern then we should generate it and save it
428
			$messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
429
			if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
430
				$this->saveRecurrencePattern();
431
			}
432
		}
433
434
		// Returns the start or end time of the occurrence on the given base date.
435
		// This assumes that the basedate you supply is in LOCAL time
436
		public function getOccurrenceStart($basedate) {
437
			$daystart = $this->dayStartOf($basedate);
438
439
			return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
440
		}
441
442
		public function getOccurrenceEnd($basedate) {
443
			$daystart = $this->dayStartOf($basedate);
444
445
			return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
446
		}
447
448
		/**
449
		 * This function returns the next remindertime starting from $timestamp
450
		 * When no next reminder exists, false is returned.
451
		 *
452
		 * Note: Before saving this new reminder time (when snoozing), you must check for
453
		 *       yourself if this reminder time is earlier than your snooze time, else
454
		 *       use your snooze time and not this reminder time.
455
		 *
456
		 * @param mixed $timestamp
457
		 */
458
		public function getNextReminderTime($timestamp) {
459
			/**
460
			 * Get next item from now until forever, but max 1 item with reminder set
461
			 * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
462
			 * Here for getting next 10 occurrences assuming that next here we will be able to find
463
			 * nextreminder occurrence in 10 occurrences.
464
			 */
465
			$items = $this->getItems($timestamp, 0x7FF00000, 10, true);
466
467
			// Initially setting nextreminder to false so when no next reminder exists, false is returned.
468
			$nextreminder = false;
469
			/*
470
			 * Loop through all reminder which we get in items variable
471
			 * and check whether the remindertime is greater than timestamp.
472
			 * On the first occurrence of greater nextreminder break the loop
473
			 * and return the value to calling function.
474
			 */
475
			for ($i = 0, $len = count($items); $i < $len; ++$i) {
476
				$item = $items[$i];
477
				$tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
478
479
				// If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
480
				if ($tempnextreminder > $timestamp) {
481
					$nextreminder = $tempnextreminder;
482
					break;
483
				}
484
			}
485
486
			return $nextreminder;
487
		}
488
489
		/**
490
		 * Note: Static function, more like a utility function.
491
		 *
492
		 * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
493
		 * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
494
		 * 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
495
		 * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
496
		 *
497
		 * @param $store resource The store in which the calendar resides
498
		 * @param $calendar resource The calendar to get the items from
499
		 * @param $viewstart int Timestamp of beginning of view window
500
		 * @param $viewend int Timestamp of end of view window
501
		 * @param $propsrequested array Array of properties to return
502
		 * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
503
		 *                    expanded so that it seems that there are only many single appointments in the table.
504
		 */
505
		public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
506
			return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
507
		}
508
509
		/*
510
		 * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY
511
		 *****************************************************************************************************************
512
		 */
513
514
		/**
515
		 * Generates and stores recurrence pattern string to recurring_pattern property.
516
		 */
517
		public function saveRecurrencePattern() {
518
			// Start formatting the properties in such a way we can apply
519
			// them directly into the recurrence pattern.
520
			$type = $this->recur['type'];
521
			$everyn = $this->recur['everyn'];
522
			$start = $this->recur['start'];
523
			$end = $this->recur['end'];
524
			$term = $this->recur['term'];
525
			$numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : 0;
526
			$startocc = $this->recur['startocc'];
527
			$endocc = $this->recur['endocc'];
528
			$pattern = '';
529
			$occSingleDayRank = false;
530
			$occTimeRange = ($startocc != 0 && $endocc != 0);
531
532
			switch ($type) {
533
				// Daily
534
				case 0x0A:
535
					if ($everyn == 1) {
536
						$type = dgettext('zarafa', 'workday');
537
						$occSingleDayRank = true;
538
					}
539
					elseif ($everyn == (24 * 60)) {
540
						$type = dgettext('zarafa', 'day');
541
						$occSingleDayRank = true;
542
					}
543
					else {
544
						$everyn /= (24 * 60);
545
						$type = dgettext('zarafa', 'days');
546
						$occSingleDayRank = false;
547
					}
548
					break;
549
				// Weekly
550
				case 0x0B:
551
					if ($everyn == 1) {
552
						$type = dgettext('zarafa', 'week');
553
						$occSingleDayRank = true;
554
					}
555
					else {
556
						$type = dgettext('zarafa', 'weeks');
557
						$occSingleDayRank = false;
558
					}
559
					break;
560
				// Monthly
561
				case 0x0C:
562
					if ($everyn == 1) {
563
						$type = dgettext('zarafa', 'month');
564
						$occSingleDayRank = true;
565
					}
566
					else {
567
						$type = dgettext('zarafa', 'months');
568
						$occSingleDayRank = false;
569
					}
570
					break;
571
				// Yearly
572
				case 0x0D:
573
					if ($everyn <= 12) {
574
						$everyn = 1;
575
						$type = dgettext('zarafa', 'year');
576
						$occSingleDayRank = true;
577
					}
578
					else {
579
						$everyn = $everyn / 12;
580
						$type = dgettext('zarafa', 'years');
581
						$occSingleDayRank = false;
582
					}
583
					break;
584
			}
585
586
			// get timings of the first occurrence
587
			$firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
588
			$firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
589
590
			$start = gmdate(dgettext('zarafa', 'd-m-Y'), $firstoccstartdate);
591
			$end = gmdate(dgettext('zarafa', 'd-m-Y'), $firstoccenddate);
592
			$startocc = gmdate(dgettext('zarafa', 'G:i'), $firstoccstartdate);
593
			$endocc = gmdate(dgettext('zarafa', 'G:i'), $firstoccenddate);
594
595
			// Based on the properties, we need to generate the recurrence pattern string.
596
			// This is obviously very easy since we can simply concatenate a bunch of strings,
597
			// however this messes up translations for languages which order their words
598
			// differently.
599
			// To improve translation quality we create a series of default strings, in which
600
			// we only have to fill in the correct variables. The base string is thus selected
601
			// based on the available properties.
602
			if ($term == 0x23) {
603
				// Never ends
604
				if ($occTimeRange) {
605
					if ($occSingleDayRank) {
606
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
607
					}
608
					else {
609
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
610
					}
611
				}
612
				else {
613
					if ($occSingleDayRank) {
614
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s.'), $type, $start);
615
					}
616
					else {
617
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s.'), $everyn, $type, $start);
618
					}
619
				}
620
			}
621
			elseif ($term == 0x22) {
622
				// After a number of times
623
				if ($occTimeRange) {
624
					if ($occSingleDayRank) {
625
						$pattern = sprintf(dngettext(
626
							'zarafa',
627
							'Occurs every %s effective %s for %s occurrence from %s to %s.',
628
							'Occurs every %s effective %s for %s occurrences from %s to %s.',
629
							$numocc
630
						), $type, $start, $numocc, $startocc, $endocc);
631
					}
632
					else {
633
						$pattern = sprintf(dngettext(
634
							'zarafa',
635
							'Occurs every %s %s effective %s for %s occurrence from %s to %s.',
636
							'Occurs every %s %s effective %s for %s occurrences %s to %s.',
637
							$numocc
638
						), $everyn, $type, $start, $numocc, $startocc, $endocc);
639
					}
640
				}
641
				else {
642
					if ($occSingleDayRank) {
643
						$pattern = sprintf(dngettext(
644
							'zarafa',
645
							'Occurs every %s effective %s for %s occurrence.',
646
							'Occurs every %s effective %s for %s occurrences.',
647
							$numocc
648
						), $type, $start, $numocc);
649
					}
650
					else {
651
						$pattern = sprintf(dngettext(
652
							'zarafa',
653
							'Occurs every %s %s effective %s for %s occurrence.',
654
							'Occurs every %s %s effective %s for %s occurrences.',
655
							$numocc
656
						), $everyn, $type, $start, $numocc);
657
					}
658
				}
659
			}
660
			elseif ($term == 0x21) {
661
				// After the given enddate
662
				if ($occTimeRange) {
663
					if ($occSingleDayRank) {
664
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
665
					}
666
					else {
667
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
668
					}
669
				}
670
				else {
671
					if ($occSingleDayRank) {
672
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s effective %s until %s.'), $type, $start, $end);
673
					}
674
					else {
675
						$pattern = sprintf(dgettext('zarafa', 'Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
676
					}
677
				}
678
			}
679
680
			if (!empty($pattern)) {
681
				mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
682
			}
683
		}
684
685
		/*
686
		 * Remove an exception by base_date. This is the base date in local daystart time
687
		 */
688
		public function deleteException($base_date) {
689
			// Remove all exceptions on $base_date from the deleted and changed occurrences lists
690
691
			// Remove all items in $todelete from deleted_occurrences
692
			$new = [];
693
694
			foreach ($this->recur["deleted_occurrences"] as $entry) {
695
				if ($entry != $base_date) {
696
					$new[] = $entry;
697
				}
698
			}
699
			$this->recur["deleted_occurrences"] = $new;
700
701
			$new = [];
702
703
			foreach ($this->recur["changed_occurrences"] as $entry) {
704
				if (!$this->isSameDay($entry["basedate"], $base_date)) {
705
					$new[] = $entry;
706
				}
707
				else {
708
					$this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
709
				}
710
			}
711
712
			$this->recur["changed_occurrences"] = $new;
713
		}
714
715
		/**
716
		 * Function which saves the exception data in an attachment.
717
		 *
718
		 * @param array        $exception_props  the exception data (like any other MAPI appointment)
719
		 * @param array        $exception_recips list of recipients
720
		 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
0 ignored issues
show
Bug introduced by
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...
721
		 *
722
		 * @return array properties of the exception
723
		 */
724
		public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) {
725
			// Create new attachment.
726
			$attachment = mapi_message_createattach($this->message);
727
			$props = [];
728
			$props[PR_ATTACHMENT_FLAGS] = 2;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
729
			$props[PR_ATTACHMENT_HIDDEN] = true;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
730
			$props[PR_ATTACHMENT_LINKID] = 0;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_LINKID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
731
			$props[PR_ATTACH_FLAGS] = 0;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
732
			$props[PR_ATTACH_METHOD] = ATTACH_EMBEDDED_MSG;
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
733
			$props[PR_DISPLAY_NAME] = "Exception";
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
734
			$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
0 ignored issues
show
Bug introduced by
The constant PR_EXCEPTION_STARTTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
735
			$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
0 ignored issues
show
Bug introduced by
The constant PR_EXCEPTION_ENDTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
736
			mapi_setprops($attachment, $props);
737
738
			$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
739
740
			if ($copy_attach_from) {
741
				$attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
742
				if ($attachmentTable) {
743
					$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
Bug introduced by
The constant PR_ATTACH_LONG_FILENAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
744
745
					foreach ($attachments as $attach_props) {
746
						$attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
747
						$attach_newResourceMsg = mapi_message_createattach($imessage);
748
						mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
749
						mapi_savechanges($attach_newResourceMsg);
750
					}
751
				}
752
			}
753
754
			$props = $props + $exception_props;
755
756
			// FIXME: the following piece of code is written to fix the creation
757
			// of an exception. This is only a quickfix as it is not yet possible
758
			// to change an existing exception.
759
			// remove mv properties when needed
760
			foreach ($props as $propTag => $propVal) {
761
				if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) {
762
					unset($props[$propTag]);
763
				}
764
			}
765
766
			mapi_setprops($imessage, $props);
767
768
			$this->setExceptionRecipients($imessage, $exception_recips, true);
769
770
			mapi_savechanges($imessage);
771
			mapi_savechanges($attachment);
772
		}
773
774
		/**
775
		 * Function which deletes the attachment of an exception.
776
		 *
777
		 * @param mixed $base_date base date of the attachment. Should be in GMT. The attachment
778
		 *                         actually saves the real time of the original date, so we have
779
		 *                         to check whether it's on the same day.
780
		 */
781
		public function deleteExceptionAttachment($base_date) {
782
			$attachments = mapi_message_getattachmenttable($this->message);
783
			// Retrieve only exceptions which are stored as embedded messages
784
			$attach_res = [
785
				RES_PROPERTY,
786
				[
787
					RELOP => RELOP_EQ,
788
					ULPROPTAG => PR_ATTACH_METHOD,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
789
					VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
790
				],
791
			];
792
			$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
793
794
			foreach ($attachRows as $attachRow) {
795
				$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
796
				$exception = mapi_attach_openobj($tempattach);
797
798
				$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
0 ignored issues
show
Bug introduced by
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

798
				$data = /** @scrutinizer ignore-call */ mapi_message_getprops($exception, [$this->proptags["basedate"]]);
Loading history...
799
800
				if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
801
					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
802
				}
803
			}
804
		}
805
806
		/**
807
		 * Function which deletes all attachments of a message.
808
		 */
809
		public function deleteAttachments() {
810
			$attachments = mapi_message_getattachmenttable($this->message);
811
			$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACHMENT_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
812
813
			foreach ($attachTable as $attachRow) {
814
				if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
815
					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
816
				}
817
			}
818
		}
819
820
		/**
821
		 * Get an exception attachment based on its basedate.
822
		 *
823
		 * @param mixed $base_date
824
		 */
825
		public function getExceptionAttachment($base_date) {
826
			// Retrieve only exceptions which are stored as embedded messages
827
			$attach_res = [
828
				RES_PROPERTY,
829
				[
830
					RELOP => RELOP_EQ,
831
					ULPROPTAG => PR_ATTACH_METHOD,
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
832
					VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
833
				],
834
			];
835
			$attachments = mapi_message_getattachmenttable($this->message);
836
			$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
837
838
			if (is_array($attachRows)) {
839
				foreach ($attachRows as $attachRow) {
840
					$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
841
					$exception = mapi_attach_openobj($tempattach);
842
843
					$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
0 ignored issues
show
Bug introduced by
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

843
					$data = /** @scrutinizer ignore-call */ mapi_message_getprops($exception, [$this->proptags["basedate"]]);
Loading history...
844
845
					if (!isset($data[$this->proptags["basedate"]])) {
846
						// if no basedate found then it could be embedded message so ignore it
847
						// we need proper restriction to exclude embedded messages as well
848
						continue;
849
					}
850
851
					if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
852
						return $tempattach;
853
					}
854
				}
855
			}
856
857
			return false;
858
		}
859
860
		/**
861
		 * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
862
		 * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
863
		 * - The occurrence isn't specified as a deleted occurrence.
864
		 *
865
		 * @param array $items        reference to the array to be added to
866
		 * @param int   $start        start of timeframe in GMT TIME
867
		 * @param int   $end          end of timeframe in GMT TIME
868
		 * @param int   $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
869
		 * @param int   $startocc     start of occurrence since beginning of day in minutes
870
		 * @param int   $endocc       end of occurrence since beginning of day in minutes
871
		 * @param mixed $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
872
		 * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
873
		 */
874
		public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
875
			$exception = $this->isException($basedate);
876
			if ($exception) {
877
				return false;
878
			}
879
			$occstart = $basedate + $startocc * 60;
880
			$occend = $basedate + $endocc * 60;
881
882
			// Convert to GMT
883
			$occstart = $this->toGMT($tz, $occstart);
884
			$occend = $this->toGMT($tz, $occend);
885
886
			/**
887
			 * FIRST PART: Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
888
			 * see any part of the appointment. Partial overlaps DO match.
889
			 *
890
			 * SECOND PART: check if occurrence is not a zero duration occurrence which
891
			 * starts at 00:00 and ends on 00:00. if it is so, then process
892
			 * the occurrence and send it in response.
893
			 */
894
			if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
895
				return;
896
			}
897
898
			// Properties for this occurrence are the same as the main object,
899
			// With these properties overridden
900
			$newitem = $this->messageprops;
901
			$newitem[$this->proptags["startdate"]] = $occstart;
902
			$newitem[$this->proptags["duedate"]] = $occend;
903
			$newitem[$this->proptags["commonstart"]] = $occstart;
904
			$newitem[$this->proptags["commonend"]] = $occend;
905
			$newitem["basedate"] = $basedate;
906
907
			// If reminderonly is set, only add reminders
908
			if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
909
				return;
910
			}
911
912
			$items[] = $newitem;
913
		}
914
915
		/**
916
		 * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
917
		 *
918
		 * @param array $items reference to the array to be added to
919
		 * @param date  $start start of timeframe in GMT TIME
920
		 * @param date  $end   end of timeframe in GMT TIME
921
		 */
922
		public function processExceptionItems(&$items, $start, $end) {
923
			$limit = 0;
924
			foreach ($this->recur["changed_occurrences"] as $exception) {
925
				// Convert to GMT
926
				$occstart = $this->toGMT($this->tz, $exception["start"]);
927
				$occend = $this->toGMT($this->tz, $exception["end"]);
928
929
				// Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
930
				// see any part of the appointment. Partial overlaps DO match.
931
				if ($occstart >= $end || $occend <= $start) {
932
					continue;
933
				}
934
935
				array_push($items, $this->getExceptionProperties($exception));
936
				if (count($items) == $limit) {
937
					break;
938
				}
939
			}
940
		}
941
942
		/**
943
		 * Function which verifies if on the given date an exception, delete or change, occurs.
944
		 *
945
		 * @param mixed $basedate
946
		 *
947
		 * @return bool true - if an exception occurs on the given date, false - no exception occurs on the given date
948
		 */
949
		public function isException($basedate) {
950
			if ($this->isDeleteException($basedate)) {
951
				return true;
952
			}
953
954
			if ($this->getChangeException($basedate) != false) {
955
				return true;
956
			}
957
958
			return false;
959
		}
960
961
		/**
962
		 * Returns TRUE if there is a DELETE exception on the given base date.
963
		 *
964
		 * @param mixed $basedate
965
		 */
966
		public function isDeleteException($basedate) {
967
			// Check if the occurrence is deleted on the specified date
968
			foreach ($this->recur["deleted_occurrences"] as $deleted) {
969
				if ($this->isSameDay($deleted, $basedate)) {
970
					return true;
971
				}
972
			}
973
974
			return false;
975
		}
976
977
		/**
978
		 * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
979
		 *
980
		 * @param mixed $basedate
981
		 */
982
		public function getChangeException($basedate) {
983
			// Check if the occurrence is modified on the specified date
984
			foreach ($this->recur["changed_occurrences"] as $changed) {
985
				if ($this->isSameDay($changed["basedate"], $basedate)) {
986
					return $changed;
987
				}
988
			}
989
990
			return false;
991
		}
992
993
		/**
994
		 * Function to see if two dates are on the same day.
995
		 *
996
		 * @param date  $time1 date 1
997
		 * @param date  $time2 date 2
998
		 * @param mixed $date1
999
		 * @param mixed $date2
1000
		 *
1001
		 * @return bool Returns TRUE when both dates are on the same day
1002
		 */
1003
		public function isSameDay($date1, $date2) {
1004
			$time1 = $this->gmtime($date1);
1005
			$time2 = $this->gmtime($date2);
1006
1007
			return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
1008
		}
1009
1010
		/**
1011
		 * Function which sets recipients for an exception.
1012
		 *
1013
		 * The $exception_recips can be provided in 2 ways:
1014
		 *  - A delta which indicates which recipients must be added, removed or deleted.
1015
		 *  - A complete array of the recipients which should be applied to the message.
1016
		 *
1017
		 * The first option is preferred as it will require less work to be executed.
1018
		 *
1019
		 * @param resource $message          exception attachment of recurring item
1020
		 * @param array    $exception_recips list of recipients
1021
		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1022
		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1023
		 *                                   be applied.
1024
		 */
1025
		public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1026
			if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1027
				$this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1028
			}
1029
			else {
1030
				$this->setAllExceptionRecipients($message, $exception_recips);
1031
			}
1032
		}
1033
1034
		/**
1035
		 * Function which applies the provided delta for recipients changes to the exception.
1036
		 *
1037
		 * The $exception_recips should be an array containing the following keys:
1038
		 *  - "add": this contains an array of recipients which must be added
1039
		 *  - "remove": This contains an array of recipients which must be removed
1040
		 *  - "modify": This contains an array of recipients which must be modified
1041
		 *
1042
		 * @param resource $message          exception attachment of recurring item
1043
		 * @param array    $exception_recips list of recipients
1044
		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1045
		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1046
		 *                                   be applied.
1047
		 * @param mixed    $exception
1048
		 */
1049
		public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) {
1050
			// Check if the recipients from the original message should be copied,
1051
			// if so, open the recipient table of the parent message and apply all
1052
			// rows on the target recipient.
1053
			if ($copy_orig_recips === true) {
1054
				$origTable = mapi_message_getrecipienttable($this->message);
1055
				$recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
1056
				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
1057
			}
1058
1059
			// Add organizer to meeting only if it is not organized.
1060
			$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
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1061
			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1062
				$this->addOrganizer($msgprops, $exception_recips['add']);
1063
			}
1064
1065
			// Remove all deleted recipients
1066
			if (isset($exception_recips['remove'])) {
1067
				foreach ($exception_recips['remove'] as &$recip) {
1068
					if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1069
						$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1070
					}
1071
					else {
1072
						$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1073
					}
1074
					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;		// No Response required
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1075
				}
1076
				unset($recip);
1077
				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1078
			}
1079
1080
			// Add all new recipients
1081
			if (isset($exception_recips['add'])) {
1082
				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1083
			}
1084
1085
			// Modify the existing recipients
1086
			if (isset($exception_recips['modify'])) {
1087
				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1088
			}
1089
		}
1090
1091
		/**
1092
		 * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1093
		 *
1094
		 * The $exception_recips should be an array containing all recipients which must be applied
1095
		 * to the exception. This will copy all recipients from the original message and then start filter
1096
		 * out all recipients which are not provided by the $exception_recips list.
1097
		 *
1098
		 * @param resource $message          exception attachment of recurring item
1099
		 * @param array    $exception_recips list of recipients
1100
		 */
1101
		public function setAllExceptionRecipients($message, $exception_recips) {
1102
			$deletedRecipients = [];
1103
			$useMessageRecipients = false;
1104
1105
			$recipientTable = mapi_message_getrecipienttable($message);
1106
			$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1107
1108
			if (empty($recipientRows)) {
1109
				$useMessageRecipients = true;
1110
				$recipientTable = mapi_message_getrecipienttable($this->message);
1111
				$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1112
			}
1113
1114
			// Add organizer to meeting only if it is not organized.
1115
			$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
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1116
			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1117
				$this->addOrganizer($msgprops, $exception_recips);
1118
			}
1119
1120
			if (!empty($exception_recips)) {
1121
				foreach ($recipientRows as $key => $recipient) {
1122
					$found = false;
1123
					foreach ($exception_recips as $excep_recip) {
1124
						if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1125
							$found = true;
1126
						}
1127
					}
1128
1129
					if (!$found) {
1130
						$foundInDeletedRecipients = false;
1131
						// Look if the $recipient is in the list of deleted recipients
1132
						if (!empty($deletedRecipients)) {
1133
							foreach ($deletedRecipients as $recip) {
1134
								if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1135
									$foundInDeletedRecipients = true;
1136
									break;
1137
								}
1138
							}
1139
						}
1140
1141
						// If recipient is not in list of deleted recipient, add him
1142
						if (!$foundInDeletedRecipients) {
1143
							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1144
								$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1145
							}
1146
							else {
1147
								$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1148
							}
1149
							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;	// No Response required
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1150
							$deletedRecipients[] = $recipient;
1151
						}
1152
					}
1153
1154
					// When $message contains a non-empty recipienttable, we must delete the recipients
1155
					// before re-adding them. However, when $message is doesn't contain any recipients,
1156
					// we are using the recipient table of the original message ($this->message)
1157
					// rather then $message. In that case, we don't need to remove the recipients
1158
					// from the $message, as the recipient table is already empty, and
1159
					// mapi_message_modifyrecipients() will throw an error.
1160
					if ($useMessageRecipients === false) {
1161
						mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1162
					}
1163
				}
1164
				$exception_recips = array_merge($exception_recips, $deletedRecipients);
1165
			}
1166
			else {
1167
				$exception_recips = $recipientRows;
1168
			}
1169
1170
			if (!empty($exception_recips)) {
1171
				// Set the new list of recipients on the exception message, this also removes the existing recipients
1172
				mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $exception_recips);
1173
			}
1174
		}
1175
1176
		/**
1177
		 * Function returns basedates of all changed occurrences.
1178
		 *
1179
		 * @return array|bool array(
1180
		 *                        0 => 123459321
1181
		 *                    )
1182
		 */
1183
		public function getAllExceptions() {
1184
			if (!empty($this->recur["changed_occurrences"])) {
1185
				$result = [];
1186
				foreach ($this->recur["changed_occurrences"] as $exception) {
1187
					$result[] = $exception["basedate"];
1188
				}
1189
1190
				return $result;
1191
			}
1192
1193
			return false;
1194
		}
1195
1196
		/**
1197
		 *  Function which adds organizer to recipient list which is passed.
1198
		 *  This function also checks if it has organizer.
1199
		 *
1200
		 * @param array $messageProps message properties
1201
		 * @param array $recipients   recipients list of message
1202
		 * @param bool  $isException  true if we are processing recipient of exception
1203
		 */
1204
		public function addOrganizer($messageProps, &$recipients, $isException = false) {
1205
			$hasOrganizer = false;
1206
			// Check if meeting already has an organizer.
1207
			foreach ($recipients as $key => $recipient) {
1208
				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1209
					$hasOrganizer = true;
1210
				}
1211
				elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1212
					// Recipients for an occurrence
1213
					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1214
				}
1215
			}
1216
1217
			if (!$hasOrganizer) {
1218
				// Create organizer.
1219
				$organizer = [];
1220
				$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1221
				$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1222
				$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1223
				$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1224
				$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1225
				$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
0 ignored issues
show
Bug introduced by
The constant PR_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_ADDRTYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1226
				$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
0 ignored issues
show
Bug introduced by
The constant PR_RECIPIENT_TRACKSTATUS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1227
				$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1228
				$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
0 ignored issues
show
Bug introduced by
The constant PR_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_SEARCH_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1229
1230
				// Add organizer to recipients list.
1231
				array_unshift($recipients, $organizer);
1232
			}
1233
		}
1234
	}
1235
1236
	/*
1237
1238
	From http://www.ohelp-one.com/new-6765483-3268.html:
1239
1240
	Recurrence Data Structure Offset Type Value
1241
1242
	0 ULONG (?) Constant : { 0x04, 0x30, 0x04, 0x30}
1243
1244
	4 UCHAR 0x0A + recurrence type: 0x0A for daily, 0x0B for weekly, 0x0C for
1245
	monthly, 0x0D for yearly
1246
1247
	5 UCHAR Constant: { 0x20}
1248
1249
	6 ULONG Seems to be a variant of the recurrence type: 1 for daily every n
1250
	days, 2 for daily every weekday and weekly, 3 for monthly or yearly. The
1251
	special exception is regenerating tasks that regenerate on a weekly basis: 0
1252
	is used in that case (I have no idea why).
1253
1254
	Here's the recurrence-type-specific data. Because the daily every N days
1255
	data are 4 bytes shorter than the data for the other types, the offsets for
1256
	the rest of the data will be 4 bytes off depending on the recurrence type.
1257
1258
	Daily every N days:
1259
1260
	10 ULONG ( N - 1) * ( 24 * 60). I'm not sure what this is used for, but it's consistent.
1261
1262
	14 ULONG N * 24 * 60: minutes between recurrences
1263
1264
	18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1265
	regenerating tasks.
1266
1267
	Daily every weekday (this is essentially a subtype of weekly recurrence):
1268
1269
	10 ULONG 6 * 24 * 60: minutes between recurrences ( a week... sort of)
1270
1271
	14 ULONG 1: recur every week (corresponds to the second parameter for weekly
1272
	recurrence)
1273
1274
	18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1275
	regenerating tasks.
1276
1277
	22 ULONG 0x3E: bitmask for recurring every weekday (corresponds to fourth
1278
	parameter for weekly recurrence)
1279
1280
	Weekly every N weeks for all events and non-regenerating tasks:
1281
1282
	10 ULONG 6 * 24 * 60: minutes between recurrences (a week... sort of)
1283
1284
	14 ULONG N: recurrence interval
1285
1286
	18 ULONG Constant: 0
1287
1288
	22 ULONG Bitmask for determining which days of the week the event recurs on
1289
	( 1 << dayOfWeek, where Sunday is 0).
1290
1291
	Weekly every N weeks for regenerating tasks: 10 ULONG Constant: 0
1292
1293
	14 ULONG N * 7 * 24 * 60: recurrence interval in minutes between occurrences
1294
1295
	18 ULONG Constant: 1
1296
1297
	Monthly every N months on day D:
1298
1299
	10 ULONG This is the most complicated value
1300
	in the entire mess. It's basically a very complicated way of stating the
1301
	recurrence interval. I tweaked fbs' basic algorithm. DateTime::MonthInDays
1302
	simply returns the number of days in a given month, e.g. 31 for July for 28
1303
	for February (the algorithm doesn't take into account leap years, but it
1304
	doesn't seem to matter). My DateTime object, like Microsoft's COleDateTime,
1305
	uses 1-based months (i.e. January is 1, not 0). With that in mind, this
1306
	works:
1307
1308
	long monthIndex = ( ( ( ( 12 % schedule-=GetInterval()) *
1309
1310
	( ( schedule-=GetStartDate().GetYear() - 1601) %
1311
1312
	schedule-=GetInterval())) % schedule-=GetInterval()) +
1313
1314
	( schedule-=GetStartDate().GetMonth() - 1)) % schedule-=GetInterval();
1315
1316
	for( int i = 0; i < monthIndex; i++)
1317
1318
	{
1319
1320
	value += DateTime::GetDaysInMonth( ( i % 12) + 1) * 24 * 60;
1321
1322
	}
1323
1324
	This should work for any recurrence interval, including those greater than
1325
	12.
1326
1327
	14 ULONG N: recurrence interval
1328
1329
	18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1330
	regenerating tasks.
1331
1332
	22 ULONG D: day of month the event recurs on (if this value is greater than
1333
	the number of days in a given month [e.g. 31 for and recurs in June], then
1334
	the event will recur on the last day of the month)
1335
1336
	Monthly every N months on the Xth Y (e.g. "2nd Tuesday"):
1337
1338
	10 ULONG See above: same as for monthly every N months on day D
1339
1340
	14 ULONG N: recurrence interval
1341
1342
	18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1343
	regenerating tasks.
1344
1345
	22 ULONG Y: bitmask for determining which day of the week the event recurs
1346
	on (see weekly every N weeks). Some useful values are 0x7F for any day, 0x3E
1347
	for a weekday, or 0x41 for a weekend day.
1348
1349
	26 ULONG X: 1 for first occurrence, 2 for second, etc. 5 for last
1350
	occurrence. E.g. for "2nd Tuesday", you should have values of 0x04 for the
1351
	prior value and 2 for this one.
1352
1353
	Yearly on day D of month M:
1354
1355
	10 ULONG M (sort of): This is another messy
1356
	value. It's the number of minute since the startning of the year to the
1357
	given month. For an explanation of GetDaysInMonth, see monthly every N
1358
	months. This will work:
1359
1360
	ULONG monthOfYearInMinutes = 0;
1361
1362
	for( int i = DateTime::cJanuary; i < schedule-=GetMonth(); i++)
1363
1364
	{
1365
1366
	monthOfYearInMinutes += DateTime::GetDaysInMonth( i) * 24 * 60;
1367
1368
	}
1369
1370
1371
1372
	14 ULONG 12: recurrence interval in months. Naturally, 12.
1373
1374
	18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
1375
	regenerating tasks.
1376
1377
	22 ULONG D: day of month the event recurs on. See monthly every N months on
1378
	day D.
1379
1380
	Yearly on the Xth Y of month M: 10 ULONG M (sort of): See yearly on day D of
1381
	month M.
1382
1383
	14 ULONG 12: recurrence interval in months. Naturally, 12.
1384
1385
	18 ULONG Constant: 0
1386
1387
	22 ULONG Y: see monthly every N months on the Xth Y.
1388
1389
	26 ULONG X: see monthly every N months on the Xth Y.
1390
1391
	After these recurrence-type-specific values, the offsets will change
1392
	depending on the type. For every type except daily every N days, the offsets
1393
	will grow by at least 4. For those types using the Xth Y, the offsets will
1394
	grow by an additional 4, for a total of 8. The offsets for the rest of these
1395
	values will be given for the most basic case, daily every N days, i.e.
1396
	without any growth. Adjust as necessary. Also, the presence of exceptions
1397
	will change the offsets following the exception data by a variable number of
1398
	bytes, so the offsets given in the table are accurate only for those
1399
	recurrence patterns without any exceptions.
1400
1401
1402
	22 UCHAR Type of pattern termination: 0x21 for terminating on a given date, 0x22 for terminating
1403
	after a given number of recurrences, or 0x23 for never terminating
1404
	(recurring infinitely)
1405
1406
	23 UCHARx3 Constant: { 0x20, 0x00, 0x00}
1407
1408
	26 ULONG Number of occurrences in pattern: 0 for infinite recurrence,
1409
	otherwise supply the value, even if it terminates on a given date, not after
1410
	a given number
1411
1412
	30 ULONG Constant: 0
1413
1414
	34 ULONG Number of exceptions to pattern (i.e. deleted or changed
1415
	occurrences)
1416
1417
	.... ULONGxN Base date of each exception, given in hundreds of nanoseconds
1418
	since 1601, so see below to turn them into a comprehensible format. The base
1419
	date of an exception is the date (and only the date-- not the time) the
1420
	exception would have occurred on in the pattern. They must occur in
1421
	ascending order.
1422
1423
	38 ULONG Number of changed exceptions (i.e. total number of exceptions -
1424
	number of deleted exceptions): if there are changed exceptions, again, more
1425
	data will be needed, but that will wait
1426
1427
	.... ULONGxN Start date (and only the date-- not the time) of each changed
1428
	exception, i.e. the exceptions which aren't deleted. These must also occur
1429
	in ascending order. If all of the exceptions are deleted, this data will be
1430
	absent. If present, they will be in the format above. Any dates that are in
1431
	the first list but not in the second are exceptions that have been deleted
1432
	(i.e. the difference between the two sets). Note that this is the start date
1433
	(including time), not the base date. Given that the values are unordered and
1434
	that they can't be matched up against the previous list in this iteration of
1435
	the recurrence data (they could in previous ones), it is very difficult to
1436
	tell which exceptions are deleted and which are changed. Fortunately, for
1437
	this new format, the base dates are given on the attachment representing the
1438
	changed exception (described below), so you can simply ignore this list of
1439
	changed exceptions. Just create a list of exceptions from the previous list
1440
	and assume they're all deleted unless you encounter an attachment with a
1441
	matching base date later on.
1442
1443
	42 ULONG Start date of pattern given in hundreds of nanoseconds since 1601;
1444
	see below for an explanation.
1445
1446
	46 ULONG End date of pattern: see start date of pattern
1447
1448
	50 ULONG Constant: { 0x06, 0x30, 0x00, 0x00}
1449
1450
	NOTE: I find the following 8-byte sequence of bytes to be very useful for
1451
	orienting myself when looking at the raw data. If you can find { 0x06, 0x30,
1452
	0x00, 0x00, 0x08, 0x30, 0x00, 0x00}, you can use these tables to work either
1453
	forwards or backwards to find the data you need. The sequence sort of
1454
	delineates certain critical exception-related data and delineates the
1455
	exceptions themselves from the rest of the data and is relatively easy to
1456
	find. If you're going to be meddling in here a lot, I suggest making a
1457
	friend of ol' 0x00003006.
1458
1459
	54 UCHAR This number is some kind of version indicator. Use 0x08 for Outlook
1460
	2003. I believe 0x06 is Outlook 2000 and possibly 98, while 0x07 is Outlook
1461
	XP. This number must be consistent with the features of the data structure
1462
	generated by the version of Outlook indicated thereby-- there are subtle
1463
	differences between the structures, and, if the version doesn't match the
1464
	data, Outlook will sometimes failto read the structure.
1465
1466
	55 UCHARx3 Constant: { 0x30, 0x00, 0x00}
1467
1468
	58 ULONG Start time of occurrence in minutes: e.g. 0 for midnight or 720 for
1469
	12 PM
1470
1471
	62 ULONG End time of occurrence in minutes: i.e. start time + duration, e.g.
1472
	900 for an event that starts at 12 PM and ends at 3PM
1473
1474
	Exception Data 66 USHORT Number of changed exceptions: essentially a check
1475
	on the prior occurrence of this value; should be equivalent.
1476
1477
	NOTE: The following structure will occur N many times (where N = number of
1478
	changed exceptions), and each structure can be of variable length.
1479
1480
	.... ULONG Start date of changed exception given in hundreds of nanoseconds
1481
	since 1601
1482
1483
	.... ULONG End date of changed exception given in hundreds of nanoseconds
1484
	since 1601
1485
1486
	.... ULONG This is a value I don't clearly understand. It seems to be some
1487
	kind of archival value that matches the start time most of the time, but
1488
	will lag behind when the start time is changed and then match up again under
1489
	certain conditions later. In any case, setting to the same value as the
1490
	start time seems to work just fine (more information on this value would be
1491
	appreciated).
1492
1493
	.... USHORT Bitmask of changes to the exception (see below). This will be 0
1494
	if the only changes to the exception were to its start or end time.
1495
1496
	.... ULONGxN Numeric values (e.g. label or minutes to remind before the
1497
	event) changed in the exception. These will occur in the order of their
1498
	corresponding bits (see below). If no numeric values were changed, then
1499
	these values will be absent.
1500
1501
	NOTE: The following three values constitute a single sub-structure that will
1502
	occur N many times, where N is the number of strings that are changed in the
1503
	exception. Since there are at most 2 string values that can be excepted
1504
	(i.e. subject [or description], and location), there can at most be two of
1505
	these, but there may be none.
1506
1507
	.... USHORT Length of changed string value with NULL character
1508
1509
	.... USHORT Length of changed string value without NULL character (i.e.
1510
	previous value - 1)
1511
1512
	.... CHARxN Changed string value (without NULL terminator)
1513
1514
	Unicode Data NOTE: If a string value was changed on an exception, those
1515
	changed string values will reappear here in Unicode format after 8 bytes of
1516
	NULL padding (possibly a Unicode terminator?). For each exception with a
1517
	changed string value, there will be an identifier, followed by the changed
1518
	strings in Unicode. The strings will occur in the order of their
1519
	corresponding bits (see below). E.g., if both subject and location were
1520
	changed in the exception, there would be the 3-ULONG identifier, then the
1521
	length of the subject, then the subject, then the length of the location,
1522
	then the location.
1523
1524
	70 ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}. This
1525
	padding serves as a barrier between the older data structure and the
1526
	appended Unicode data. This is the same sequence as the Unicode terminator,
1527
	but I'm not sure whether that's its identity or not.
1528
1529
	.... ULONGx3 These are the three times used to identify the exception above:
1530
	start date, end date, and repeated start date. These should be the same as
1531
	they were above.
1532
1533
	.... USHORT Length of changed string value without NULL character. This is
1534
	given as count of WCHARs, so it should be identical to the value above.
1535
1536
	.... WCHARxN Changed string value in Unicode (without NULL terminator)
1537
1538
	Terminator ... ULONGxN Constant: { 0x00, 0x00, 0x00, 0x00}. 4 bytes of NULL
1539
	padding per changed exception. If there were no changed exceptions, all
1540
	you'll need is the final terminator below.
1541
1542
	.... ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}.
1543
1544
	*/
1545