TaskRecurrence::processOccurrenceItem()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 3
nop 8
dl 0
loc 14
rs 10

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

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

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