TaskRecurrence::processOccurrenceItem()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 3
eloc 9
c 2
b 1
f 0
nc 3
nop 8
dl 0
loc 17
rs 9.9666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
7
 */
8
9
class TaskRecurrence extends BaseRecurrence {
10
	/**
11
	 * Timezone info which is always false for task.
12
	 *
13
	 * @var false
14
	 */
15
	public $tz = false;
16
17
	private $action;
18
19
	public function __construct(mixed $store, mixed $message) {
20
		$this->store = $store;
21
		$this->message = $message;
22
23
		$properties = [];
24
		$properties["entryid"] = PR_ENTRYID;
25
		$properties["parent_entryid"] = PR_PARENT_ENTRYID;
26
		$properties["icon_index"] = PR_ICON_INDEX;
27
		$properties["message_class"] = PR_MESSAGE_CLASS;
28
		$properties["message_flags"] = PR_MESSAGE_FLAGS;
29
		$properties["subject"] = PR_SUBJECT;
30
		$properties["importance"] = PR_IMPORTANCE;
31
		$properties["sensitivity"] = PR_SENSITIVITY;
32
		$properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
33
		$properties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus;
34
		$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete;
35
		$properties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate;
36
		$properties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate;
37
		$properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
38
		$properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
39
		$properties["date_completed"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted;
40
		$properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
41
		$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
42
		$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
43
		$properties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete;
44
		$properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
45
		$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
46
		$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
47
48
		$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:" . PidLidReminderDelta;
49
		$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderTime;
50
		$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:" . PidLidReminderSet;
51
52
		$properties["private"] = "PT_BOOLEAN:PSETID_Common:" . PidLidPrivate;
53
		$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
54
		$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
55
		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
56
57
		$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
58
		$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
59
		$properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
60
		$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderSignalTime;
61
		$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
62
63
		$this->proptags = getPropIdsFromStrings($store, $properties);
64
65
		parent::__construct($store, $message);
66
	}
67
68
	/**
69
	 * Function which saves recurrence and also regenerates task if necessary.
70
	 *
71
	 * @param mixed $recur new recurrence properties
72
	 *
73
	 * @return array|bool of properties of regenerated task else false
74
	 */
75
	public function setRecurrence(mixed &$recur): array|bool {
76
		$this->recur = $recur;
77
		$this->action = &$recur;
78
79
		$this->recur["changed_occurrences"] ??= [];
80
		$this->recur["deleted_occurrences"] ??= [];
81
		$this->recur['startocc'] ??= 0;
82
		$this->recur['endocc'] ??= 0;
83
84
		// Save recurrence because we need proper startrecurrdate and endrecurrdate
85
		$this->saveRecurrence();
86
87
		// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
88
		$msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
89
		$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
90
		foreach ($recurring_data as $key => $value) {
91
			$this->recur[$key] = $value;
92
		}
93
94
		$this->setFirstOccurrence();
95
96
		// Let's see if next occurrence has to be generated
97
		return $this->moveToNextOccurrence();
98
	}
99
100
	/**
101
	 * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
102
	 */
103
	public function setFirstOccurrence(): void {
104
		// Check if it is already the first occurrence
105
		if ($this->action['start'] == $this->recur["start"]) {
106
			return;
107
		}
108
		$items = $this->getNextOccurrence();
109
110
		$props = [];
111
		$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
112
		$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
113
114
		$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
115
		$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
116
117
		mapi_setprops($this->message, $props);
118
	}
119
120
	/**
121
	 * Function which creates new task as current occurrence and moves the
122
	 * existing task to next occurrence.
123
	 *
124
	 * @return array|bool properties of newly created task if moving to next occurrence succeeds
125
	 *                    false if that was last occurrence
126
	 */
127
	public function moveToNextOccurrence(): array|bool {
128
		$result = false;
129
		/*
130
		 * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
131
		 * then we create first two occurrence separately and for first occurrence recurrence has ended.
132
		 */
133
		if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
134
			($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
135
			$nextOccurrence = $this->getNextOccurrence();
136
			$result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
137
138
			$props = [];
139
			if (!empty($nextOccurrence)) {
140
				if (!isset($this->action['deleteOccurrence'])) {
141
					// Create current occurrence as separate task
142
					$result = $this->regenerateTask($this->action['complete']);
143
				}
144
145
				// Set reminder for next occurrence
146
				$this->setReminder($nextOccurrence);
147
148
				// Update properties for next occurrence
149
				$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
150
				$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
151
152
				$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
153
				$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
154
155
				// If current task as been mark as 'Complete' then next occurrence should be incomplete.
156
				if (isset($this->action['complete']) && $this->action['complete'] == 1) {
157
					$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
158
					$this->action['complete'] = $props[$this->proptags["complete"]] = false;
159
					$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
160
				}
161
162
				$props[$this->proptags["dead_occurrence"]] = false;
163
			}
164
			else {
165
				if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
166
					return false;
167
				}
168
169
				// Didn't get next occurrence, probably this is the last one, so recurrence ends here
170
				$props[$this->proptags["dead_occurrence"]] = true;
171
				$props[$this->proptags["date_completed"]] = $this->action['date_completed'];
172
				$props[$this->proptags["task_f_creator"]] = true;
173
174
				// OL props
175
				$props[$this->proptags["side_effects"]] = 1296;
176
				$props[$this->proptags["icon_index"]] = 1280;
177
			}
178
179
			mapi_setprops($this->message, $props);
180
		}
181
182
		return $result;
183
	}
184
185
	/**
186
	 * Function which return properties of next occurrence.
187
	 *
188
	 * @return array|false|null startdate/enddate of next occurrence
189
	 */
190
	public function getNextOccurrence(): mixed {
191
		if ($this->recur) {
192
			// @TODO: fix start of range
193
			$start = $this->messageprops[$this->proptags["duedate"]] ?? $this->action['start'];
194
			$dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
195
196
			// Fix recur object
197
			$this->recur['startocc'] = 0;
198
			$this->recur['endocc'] = 0;
199
200
			// Retrieve next occurrence
201
			$items = $this->getItems($start, $dayend, 1);
202
203
			return !empty($items) ? $items[0] : false;
204
		}
205
206
		return null;
207
	}
208
209
	/**
210
	 * Function which clones current occurrence and sets appropriate properties.
211
	 * The original recurring item is moved to next occurrence.
212
	 *
213
	 * @param bool $markComplete true if existing occurrence has to be marked complete
214
	 */
215
	public function regenerateTask(bool $markComplete): array {
216
		// Get all properties
217
		$taskItemProps = mapi_getprops($this->message);
218
219
		if (isset($this->action["subject"])) {
220
			$taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
221
		}
222
		if (isset($this->action["importance"])) {
223
			$taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
224
		}
225
		if (isset($this->action["startdate"])) {
226
			$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
227
			$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
228
		}
229
		if (isset($this->action["duedate"])) {
230
			$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
231
			$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
232
		}
233
234
		$folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
0 ignored issues
show
Bug introduced by
$this->store of type resource is incompatible with the type resource expected by parameter $store of mapi_msgstore_openentry(). ( Ignorable by Annotation )

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

234
		$folder = mapi_msgstore_openentry(/** @scrutinizer ignore-type */ $this->store, $taskItemProps[PR_PARENT_ENTRYID]);
Loading history...
235
		$newMessage = mapi_folder_createmessage($folder);
236
237
		$taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
238
		$taskItemProps[$this->proptags["complete"]] = $markComplete;
239
		$taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
240
241
		// This occurrence has been marked as 'Complete' so disable reminder
242
		if ($markComplete) {
243
			$taskItemProps[$this->proptags["reset_reminder"]] = false;
244
			$taskItemProps[$this->proptags["reminder"]] = false;
245
			$taskItemProps[$this->proptags["date_completed"]] = $this->action["date_completed"];
246
247
			unset($this->action[$this->proptags['date_completed']]);
248
		}
249
250
		// Recurrence ends for this item
251
		$taskItemProps[$this->proptags["dead_occurrence"]] = true;
252
		$taskItemProps[$this->proptags["task_f_creator"]] = true;
253
254
		// OL props
255
		$taskItemProps[$this->proptags["side_effects"]] = 1296;
256
		$taskItemProps[$this->proptags["icon_index"]] = 1280;
257
258
		// Copy recipients
259
		$recipienttable = mapi_message_getrecipienttable($this->message);
260
		$recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]);
261
262
		$copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
263
		$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
264
		foreach ($copy_to_recipientRows as $recipient) {
265
			mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]);
266
		}
267
		mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
268
269
		// Copy attachments
270
		$attachmentTable = mapi_message_getattachmenttable($this->message);
271
		if ($attachmentTable) {
0 ignored issues
show
introduced by
$attachmentTable is of type resource, thus it always evaluated to true.
Loading history...
272
			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
273
274
			foreach ($attachments as $attach_props) {
275
				$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
276
				$attach_newResourceMsg = mapi_message_createattach($newMessage);
277
278
				mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
279
				mapi_savechanges($attach_newResourceMsg);
280
			}
281
		}
282
283
		mapi_setprops($newMessage, $taskItemProps);
284
		mapi_savechanges($newMessage);
285
286
		// Update body of original message
287
		$msgbody = mapi_openproperty($this->message, PR_BODY);
288
		$msgbody = trim($msgbody, "\0");
0 ignored issues
show
Bug introduced by
$msgbody of type resource is incompatible with the type string expected by parameter $string of trim(). ( Ignorable by Annotation )

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

288
		$msgbody = trim(/** @scrutinizer ignore-type */ $msgbody, "\0");
Loading history...
289
		$separator = "------------\r\n";
290
291
		if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
292
			$msgbody = $separator . $msgbody;
293
			$stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0);
294
			mapi_stream_setsize($stream, strlen($msgbody));
295
			mapi_stream_write($stream, $msgbody);
296
			mapi_stream_commit($stream);
297
		}
298
299
		// We need these properties to notify client
300
		return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
301
	}
302
303
	/**
304
	 * processOccurrenceItem, adds an item to a list of occurrences, but only if the
305
	 * resulting occurrence starts or ends in the interval <$start, $end>.
306
	 *
307
	 * @param array $items        reference to the array to be added to
308
	 * @param int   $start        start of timeframe in GMT TIME
309
	 * @param int   $end          end of timeframe in GMT TIME
310
	 * @param int   $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
311
	 * @param int   $startocc     start of occurrence since beginning of day in minutes
312
	 * @param int   $endocc       end of occurrence since beginning of day in minutes
313
	 * @param int   $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
314
	 * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
315
	 */
316
	public function processOccurrenceItem(array &$items, mixed $start, int $end, mixed $basedate, mixed $startocc, mixed $endocc, mixed $tz, mixed $reminderonly): ?false {
317
		if ($basedate > $start) {
318
			$newItem = [];
319
			$newItem[$this->proptags['startdate']] = $basedate;
320
321
			// If startdate and enddate are set on task, then slide enddate according to duration
322
			if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
323
				$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
324
			}
325
			else {
326
				$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
327
			}
328
329
			$items[] = $newItem;
330
		}
331
332
		return null;
333
	}
334
335
	/**
336
	 * Function which marks existing occurrence to 'Complete'.
337
	 *
338
	 * @param array $recur array action from client
339
	 *
340
	 * @return array|bool of properties of regenerated task else false
341
	 */
342
	public function markOccurrenceComplete(array &$recur): array|bool {
343
		// Fix timezone object
344
		$this->tz = false;
345
		$this->action = &$recur;
346
		$dead_occurrence = $this->messageprops[$this->proptags['dead_occurrence']] ?? false;
347
348
		if (!$dead_occurrence) {
349
			return $this->moveToNextOccurrence();
350
		}
351
352
		return false;
353
	}
354
355
	/**
356
	 * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
357
	 *
358
	 * @param mixed $nextOccurrence properties of next occurrence
359
	 */
360
	public function setReminder(mixed $nextOccurrence): void {
361
		$props = [];
362
		if (!empty($nextOccurrence)) {
363
			// Check if reminder is reset. Default is 'false'
364
			$reset_reminder = $this->messageprops[$this->proptags['reset_reminder']] ?? false;
365
			$reminder = $this->messageprops[$this->proptags['reminder']];
366
367
			// Either reminder was already set OR reminder was set but was dismissed bty user
368
			if ($reminder || $reset_reminder) {
369
				// Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
370
				$reminder_time = $this->messageprops[$this->proptags['reminder_time']] ?? 0;
371
				$reminder_difference = $this->messageprops[$this->proptags['duedate']] ?? 0;
372
				$reminder_difference = $reminder_difference - $reminder_time;
373
374
				// Apply duration to next calculated duedate
375
				$next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
376
377
				$props[$this->proptags['reminder_time']] = $next_reminder_time;
378
				$props[$this->proptags['flagdueby']] = $next_reminder_time;
379
				$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
380
			}
381
		}
382
		else {
383
			// Didn't get next occurrence, probably this is the last occurrence
384
			$props[$this->proptags['reminder']] = false;
385
			$props[$this->proptags['reset_reminder']] = false;
386
		}
387
388
		if (!empty($props)) {
389
			mapi_setprops($this->message, $props);
390
		}
391
	}
392
393
	/**
394
	 * Function which recurring task to next occurrence.
395
	 * It simply doesn't regenerate task.
396
	 */
397
	public function deleteOccurrence(array $action): array|bool {
398
		$this->tz = false;
399
		$this->action = $action;
400
		$result = $this->moveToNextOccurrence();
401
402
		mapi_savechanges($this->message);
403
404
		return $result;
405
	}
406
}
407