Passed
Push — master ( 02632b...bcda7c )
by
unknown
37:01 queued 23:42
created

TaskRecurrence::processOccurrenceItem()   A

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
/*
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($store, $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["datecompleted"] = "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(&$recur) {
76
		$this->recur = $recur;
77
		$this->action = &$recur;
78
79
		if (!isset($this->recur["changed_occurrences"])) {
80
			$this->recur["changed_occurrences"] = [];
81
		}
82
83
		if (!isset($this->recur["deleted_occurrences"])) {
84
			$this->recur["deleted_occurrences"] = [];
85
		}
86
87
		if (!isset($this->recur['startocc'])) {
88
			$this->recur['startocc'] = 0;
89
		}
90
		if (!isset($this->recur['endocc'])) {
91
			$this->recur['endocc'] = 0;
92
		}
93
94
		// Save recurrence because we need proper startrecurrdate and endrecurrdate
95
		$this->saveRecurrence();
96
97
		// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
98
		$msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
99
		$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
100
		foreach ($recurring_data as $key => $value) {
101
			$this->recur[$key] = $value;
102
		}
103
104
		$this->setFirstOccurrence();
105
106
		// Let's see if next occurrence has to be generated
107
		return $this->moveToNextOccurrence();
108
	}
109
110
	/**
111
	 * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
112
	 */
113
	public function setFirstOccurrence() {
114
		// Check if it is already the first occurrence
115
		if ($this->action['start'] == $this->recur["start"]) {
116
			return;
117
		}
118
		$items = $this->getNextOccurrence();
119
120
		$props = [];
121
		$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
122
		$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
123
124
		$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
125
		$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
126
127
		mapi_setprops($this->message, $props);
128
	}
129
130
	/**
131
	 * Function which creates new task as current occurrence and moves the
132
	 * existing task to next occurrence.
133
	 *
134
	 * @return array|bool properties of newly created task if moving to next occurrence succeeds
135
	 *                    false if that was last occurrence
136
	 */
137
	public function moveToNextOccurrence() {
138
		$result = false;
139
		/*
140
		 * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
141
		 * then we create first two occurrence separately and for first occurrence recurrence has ended.
142
		 */
143
		if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
144
			($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
145
			$nextOccurrence = $this->getNextOccurrence();
146
			$result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
147
148
			$props = [];
149
			if (!empty($nextOccurrence)) {
150
				if (!isset($this->action['deleteOccurrence'])) {
151
					// Create current occurrence as separate task
152
					$result = $this->regenerateTask($this->action['complete']);
153
				}
154
155
				// Set reminder for next occurrence
156
				$this->setReminder($nextOccurrence);
157
158
				// Update properties for next occurrence
159
				$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
160
				$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
161
162
				$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
163
				$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
164
165
				// If current task as been mark as 'Complete' then next occurrence should be incomplete.
166
				if (isset($this->action['complete']) && $this->action['complete'] == 1) {
167
					$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
168
					$this->action['complete'] = $props[$this->proptags["complete"]] = false;
169
					$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
170
				}
171
172
				$props[$this->proptags["dead_occurrence"]] = false;
173
			}
174
			else {
175
				if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
176
					return false;
177
				}
178
179
				// Didn't get next occurrence, probably this is the last one, so recurrence ends here
180
				$props[$this->proptags["dead_occurrence"]] = true;
181
				$props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
182
				$props[$this->proptags["task_f_creator"]] = true;
183
184
				// OL props
185
				$props[$this->proptags["side_effects"]] = 1296;
186
				$props[$this->proptags["icon_index"]] = 1280;
187
			}
188
189
			mapi_setprops($this->message, $props);
190
		}
191
192
		return $result;
193
	}
194
195
	/**
196
	 * Function which return properties of next occurrence.
197
	 *
198
	 * @return null|array|false|T startdate/enddate of next occurrence
199
	 */
200
	public function getNextOccurrence() {
201
		if ($this->recur) {
202
			// @TODO: fix start of range
203
			$start = $this->messageprops[$this->proptags["duedate"]] ?? $this->action['start'];
204
			$dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
205
206
			// Fix recur object
207
			$this->recur['startocc'] = 0;
208
			$this->recur['endocc'] = 0;
209
210
			// Retrieve next occurrence
211
			$items = $this->getItems($start, $dayend, 1);
212
213
			return !empty($items) ? $items[0] : false;
214
		}
215
	}
216
217
	/**
218
	 * Function which clones current occurrence and sets appropriate properties.
219
	 * The original recurring item is moved to next occurrence.
220
	 *
221
	 * @param bool $markComplete true if existing occurrence has to be marked complete
222
	 */
223
	public function regenerateTask($markComplete) {
224
		// Get all properties
225
		$taskItemProps = mapi_getprops($this->message);
226
227
		if (isset($this->action["subject"])) {
228
			$taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
229
		}
230
		if (isset($this->action["importance"])) {
231
			$taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
232
		}
233
		if (isset($this->action["startdate"])) {
234
			$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
235
			$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
236
		}
237
		if (isset($this->action["duedate"])) {
238
			$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
239
			$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
240
		}
241
242
		$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

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

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