Passed
Push — master ( ae800f...172061 )
by
unknown
02:27
created

TaskRecurrence::processOccurrenceItem()   A

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 null|array|false|T 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
207
	/**
208
	 * Function which clones current occurrence and sets appropriate properties.
209
	 * The original recurring item is moved to next occurrence.
210
	 *
211
	 * @param bool $markComplete true if existing occurrence has to be marked complete
212
	 */
213
	public function regenerateTask(bool $markComplete): array {
214
		// Get all properties
215
		$taskItemProps = mapi_getprops($this->message);
216
217
		if (isset($this->action["subject"])) {
218
			$taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
219
		}
220
		if (isset($this->action["importance"])) {
221
			$taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
222
		}
223
		if (isset($this->action["startdate"])) {
224
			$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
225
			$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
226
		}
227
		if (isset($this->action["duedate"])) {
228
			$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
229
			$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
230
		}
231
232
		$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

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

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