TaskRequest::getAssociatedTask()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 74
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 6
eloc 43
c 3
b 1
f 0
nc 6
nop 1
dl 0
loc 74
rs 8.6097

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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-2024 grommunio GmbH
7
 */
8
9
/*
10
* In general
11
*
12
* This class never actually modifies a task item unless we receive a task request update. This means
13
* that setting all the properties to make the task item itself behave like a task request is up to the
14
* caller.
15
*
16
* The only exception to this is the generation of the TaskGlobalObjId, the unique identifier identifying
17
* this task request to both the organizer and the assignee. The globalobjectid is generated when the
18
* task request is sent via sendTaskRequest.
19
*/
20
21
/* The TaskMode value is only used for the IPM.TaskRequest items.
22
 * It must 0 (tdmtNothing) on IPM.Task items.
23
 *
24
 * It is used to indicate the type of change that is being
25
 * carried in the IPM.TaskRequest item (although this information seems
26
 * redundant due to that information already being available in PR_MESSAGE_CLASS).
27
 */
28
define('tdmtNothing', 0);			// Value in IPM.Task items
29
define('tdmtTaskReq', 1);			// Assigner -> Assignee
30
define('tdmtTaskAcc', 2);			// Assignee -> Assigner
31
define('tdmtTaskDec', 3);			// Assignee -> Assigner
32
define('tdmtTaskUpd', 4);			// Assignee -> Assigner
33
define('tdmtTaskSELF', 5);			// Assigner -> Assigner (?)
34
35
/* The TaskHistory is used to show the last action on the task
36
 * on both the assigner and the assignee's side.
37
 *
38
 * It is used in combination with 'task_assigned_time' and 'tasklastdelegate'
39
 * or 'tasklastuser' to show the information at the top of the task request in
40
 * the format 'Accepted by <user> on 01-01-2010 11:00'.
41
 */
42
define('thNone', 0);
43
define('thAccepted', 1);			// Set by assignee
44
define('thDeclined', 2);			// Set by assignee
45
define('thUpdated', 3);				// Set by assignee
46
define('thDueDateChanged', 4);
47
define('thAssigned', 5);			// Set by assigner
48
49
/* The TaskState value is used to differentiate the version of a task
50
 * in the assigner's folder and the version in the
51
 * assignee's folder. The buttons shown depend on this and
52
 * the 'taskaccepted' boolean (for the assignee)
53
 */
54
define('tdsNOM', 0);		// Got a response to a deleted task, and re-created the task for the assigner
55
define('tdsOWNNEW', 1);		// Not assigned
56
define('tdsOWN', 2);		// Assignee version
57
define('tdsACC', 3);		// Assigner version
58
define('tdsDEC', 4);		// Assigner version, but assignee declined
59
60
/* The TaskAcceptanceState is used for the assigner to indicate state */
61
define('olTaskNotDelegated', 0);
62
define('olTaskDelegationUnknown', 1); // After sending req
63
define('olTaskDelegationAccepted', 2); // After receiving accept
64
define('olTaskDelegationDeclined', 3); // After receiving decline
65
66
/* The task ownership indicates the role of the current user relative to the task. */
67
define('olNewTask', 0);
68
define('olDelegatedTask', 1);	// Task has been assigned
69
define('olOwnTask', 2);			// Task owned
70
71
/* taskmultrecips indicates whether the task request sent or received has multiple assignees or not. */
72
define('tmrNone', 0);
73
define('tmrSent', 1);		// Task has been sent to multiple assignee
74
define('tmrReceived', 2);	// Task Request received has multiple assignee
75
76
// Task icon index.
77
define('ICON_TASK_ASSIGNEE', 0x00000502);
78
define('ICON_TASK_DECLINE', 0x00000506);
79
define('ICON_TASK_ASSIGNER', 0x00000503);
80
81
class TaskRequest {
82
	private $props;
83
84
	/**
85
	 * @var string
86
	 */
87
	private $taskCommentsInfo;
88
89
	// All recipient properties
90
	public $recipProps = [
91
		PR_ENTRYID,
92
		PR_DISPLAY_NAME,
93
		PR_EMAIL_ADDRESS,
94
		PR_RECIPIENT_ENTRYID,
95
		PR_RECIPIENT_TYPE,
96
		PR_SEND_INTERNET_ENCODING,
97
		PR_SEND_RICH_INFO,
98
		PR_RECIPIENT_DISPLAY_NAME,
99
		PR_ADDRTYPE,
100
		PR_DISPLAY_TYPE,
101
		PR_RECIPIENT_TRACKSTATUS,
102
		PR_RECIPIENT_TRACKSTATUS_TIME,
103
		PR_RECIPIENT_FLAGS,
104
		PR_ROWID,
105
		PR_SEARCH_KEY,
106
	];
107
108
	/**
109
	 * Constructor.
110
	 *
111
	 * Constructs a TaskRequest object for the specified message. This can be either the task request
112
	 * message itself (in the inbox) or the task in the tasks folder, depending on the action to be performed.
113
	 *
114
	 * As a general rule, the object message passed is the object 'in view' when the user performs one of the
115
	 * actions in this class.
116
	 *
117
	 * @param resource $store   MAPI Store in which $message resides. This is also the store where the tasks folder is assumed to be in
118
	 * @param resource $message MAPI Message to which the task request refers (can be an email or a task)
119
	 * @param resource $session MAPI Session which is used to open tasks folders for delegated task requests or responses
120
	 */
121
	public function __construct(private $store, private $message, private $session) {
122
		$this->taskCommentsInfo = '';
123
124
		$properties = [];
125
		$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
126
		$properties["updatecount"] = "PT_LONG:PSETID_Task:0x8112";
127
		$properties["taskstate"] = "PT_LONG:PSETID_Task:0x8113";
128
		$properties["taskmultrecips"] = "PT_LONG:PSETID_Task:0x8120";
129
		$properties["taskupdates"] = "PT_BOOLEAN:PSETID_Task:0x811b";
130
		$properties["tasksoc"] = "PT_BOOLEAN:PSETID_Task:0x8119";
131
		$properties["taskhistory"] = "PT_LONG:PSETID_Task:0x811a";
132
		$properties["taskmode"] = "PT_LONG:PSETID_Common:0x8518";
133
		$properties["task_goid"] = "PT_BINARY:PSETID_Common:0x8519";
134
		$properties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete;
135
		$properties["task_assigned_time"] = "PT_SYSTIME:PSETID_Task:0x8115";
136
		$properties["taskfcreator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
137
		$properties["tasklastuser"] = "PT_STRING8:PSETID_Task:0x8122";
138
		$properties["tasklastdelegate"] = "PT_STRING8:PSETID_Task:0x8125";
139
		$properties["taskaccepted"] = "PT_BOOLEAN:PSETID_Task:0x8108";
140
		$properties["task_acceptance_state"] = "PT_LONG:PSETID_Task:0x812a";
141
		$properties["ownership"] = "PT_LONG:PSETID_Task:0x8129";
142
		$properties["date_completed"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted;
143
		$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
144
		$properties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate;
145
		$properties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate;
146
		$properties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus;
147
		$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete;
148
		$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
149
		$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
150
		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
151
		$properties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
152
		$properties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
153
		$properties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
154
155
		$this->props = getPropIdsFromStrings($this->store, $properties);
156
	}
157
158
	// General functions
159
160
	/**
161
	 * Returns TRUE if the message pointed to is an incoming task request and should
162
	 * therefore be replied to with doAccept or doDecline().
163
	 *
164
	 * @param string|false $messageClass message class to use for checking
165
	 *
166
	 * @return bool true if this is a task request else false
167
	 */
168
	public function isTaskRequest(false|string $messageClass = false): bool {
169
		if ($messageClass === false) {
170
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

170
			$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_CLASS]);
Loading history...
171
			$messageClass = $props[PR_MESSAGE_CLASS] ?? false;
172
		}
173
174
		return $messageClass !== false && $messageClass === "IPM.TaskRequest";
175
	}
176
177
	/**
178
	 * Returns TRUE if the message pointed to is a returning task request response.
179
	 *
180
	 * @param string|false $messageClass message class to use for checking
181
	 *
182
	 * @return bool true if this is a task request else false
183
	 */
184
	public function isTaskRequestResponse(false|string $messageClass = false): bool {
185
		if ($messageClass === false) {
186
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

186
			$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_CLASS]);
Loading history...
187
			$messageClass = $props[PR_MESSAGE_CLASS] ?? false;
188
		}
189
190
		return $messageClass !== false && str_starts_with((string) $messageClass, "IPM.TaskRequest.");
191
	}
192
193
	/**
194
	 * Returns TRUE if the message pointed to is an incoming task request/response.
195
	 *
196
	 * @param array $props The MAPI properties to check message is an incoming task request/response
197
	 *
198
	 * @return bool true if this is an incoming task request/response else false
199
	 */
200
	public function isReceivedItem(array $props): bool {
201
		return $props[PR_MESSAGE_TO_ME] ?? false;
202
	}
203
204
	/**
205
	 * Gets the task associated with an IPM.TaskRequest message.
206
	 *
207
	 * If the task does not exist yet, it is created, using the attachment object in the
208
	 * task request item.
209
	 *
210
	 * @param bool $create true - try create task in user's task folder if task does not exist
211
	 *                     false - find the associated task in user's task folder
212
	 *
213
	 * @return bool|resource associated task of task request else false
214
	 */
215
	public function getAssociatedTask(bool $create): mixed {
216
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['task_goid']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

216
		$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_CLASS, $this->props['task_goid']]);
Loading history...
217
218
		if ($props[PR_MESSAGE_CLASS] == "IPM.Task") {
219
			// Message itself is task, so return that
220
			return $this->message;
221
		}
222
223
		$taskFolder = $this->getDefaultTasksFolder();
224
		$goid = $props[$this->props['task_goid']];
225
226
		// Find the task by looking for the task_goid
227
		$restriction = [
228
			RES_PROPERTY,
229
			[
230
				RELOP => RELOP_EQ,
231
				ULPROPTAG => $this->props['task_goid'],
232
				VALUE => $goid,
233
			],
234
		];
235
236
		$contents = mapi_folder_getcontentstable($taskFolder);
237
238
		$rows = mapi_table_queryallrows($contents, [PR_ENTRYID], $restriction);
239
240
		if (empty($rows)) {
241
			// None found, create one if possible
242
			if (!$create) {
243
				return false;
244
			}
245
246
			$task = mapi_folder_createmessage($taskFolder);
247
248
			$sub = $this->getEmbeddedTask();
249
250
			try {
251
				mapi_copyto($sub, [], [$this->props['categories']], $task);
252
			}
253
			catch (MAPIException $e) {
254
				if ($e->getCode() !== MAPI_E_INVALID_PARAMETER) {
255
					throw $e;
256
				}
257
				// Some stores reject excluding categories during copy when attachments are present.
258
				// Retry without exclusions to keep attachments intact and drop categories afterwards.
259
				mapi_copyto($sub, [], [], $task);
260
				mapi_deleteprops($task, [$this->props['categories']]);
261
			}
262
263
			$senderProps = [
264
				PR_SENT_REPRESENTING_NAME,
265
				PR_SENT_REPRESENTING_EMAIL_ADDRESS,
266
				PR_SENT_REPRESENTING_ENTRYID,
267
				PR_SENT_REPRESENTING_ADDRTYPE,
268
				PR_SENT_REPRESENTING_SEARCH_KEY,
269
				PR_SENDER_NAME,
270
				PR_SENDER_EMAIL_ADDRESS,
271
				PR_SENDER_ENTRYID,
272
				PR_SENDER_ADDRTYPE,
273
				PR_SENDER_SEARCH_KEY, ];
274
275
			// Copy sender information from the e-mail
276
			$props = mapi_getprops($this->message, $senderProps);
277
			$props[PR_MESSAGE_CLASS] = 'IPM.Task';
278
			mapi_setprops($task, $props);
279
		}
280
		else {
281
			// If there are multiple, just use the first
282
			$entryid = $rows[0][PR_ENTRYID];
283
284
			$store = $this->getTaskFolderStore();
285
			$task = mapi_msgstore_openentry($store, $entryid);
286
		}
287
288
		return $task;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $task returns the type resource which is incompatible with the documented return type boolean|resource.
Loading history...
289
	}
290
291
	/**
292
	 * Function which checks that if we have received a task request/response
293
	 * for an already updated task in task folder.
294
	 *
295
	 * @return bool true if task request is updated later
296
	 */
297
	public function isTaskRequestUpdated(): bool {
298
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['task_goid'], $this->props['updatecount']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

298
		$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_CLASS, $this->props['task_goid'], $this->props['updatecount']]);
Loading history...
299
		$result = false;
300
		$associatedTask = $this->getAssociatedTask(false);
301
		if ($this->isTaskRequest($props[PR_MESSAGE_CLASS]) && $associatedTask) {
302
			return true;
303
		}
304
		if ($this->isTaskRequest($props[PR_MESSAGE_CLASS])) {
305
			$folder = $this->getDefaultTasksFolder();
306
			$goid = $props[$this->props['task_goid']];
307
308
			// Find the task by looking for the task_goid
309
			$restriction = [
310
				RES_PROPERTY,
311
				[
312
					RELOP => RELOP_EQ,
313
					ULPROPTAG => $this->props['task_goid'],
314
					VALUE => $goid,
315
				],
316
			];
317
318
			$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS | SHOW_SOFT_DELETES);
319
			$softDeletedItems = mapi_table_queryallrows($table, [PR_ENTRYID], $restriction);
320
			if (!empty($softDeletedItems)) {
321
				return true;
322
			}
323
		}
324
325
		if ($associatedTask !== false) {
326
			$taskItemProps = mapi_getprops($associatedTask, [$this->props['updatecount']]);
327
			/*
328
			 * if(message_counter < task_counter) task object is newer then task response (task is updated)
329
			 * if(message_counter >= task_counter) task is not updated, do normal processing
330
			 */
331
			if (isset($taskItemProps[$this->props['updatecount']], $props[$this->props['updatecount']])) {
332
				$result = $props[$this->props['updatecount']] < $taskItemProps[$this->props['updatecount']];
333
			}
334
		}
335
336
		return $result;
337
	}
338
339
	// Organizer functions (called by the organizer)
340
341
	/**
342
	 * Processes a task request response, which can be any of the following:
343
	 * - Task accept (task history is marked as accepted)
344
	 * - Task decline (task history is marked as declined)
345
	 * - Task update (updates completion %, etc).
346
	 *
347
	 * @return true
348
	 */
349
	public function processTaskResponse(): bool {
350
		$messageProps = mapi_getprops($this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

350
		$messageProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
Loading history...
351
		if (isset($messageProps[PR_PROCESSED]) && $messageProps[PR_PROCESSED]) {
352
			return true;
353
		}
354
		mapi_setprops($this->message, [PR_PROCESSED => true]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

354
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [PR_PROCESSED => true]);
Loading history...
355
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

355
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
356
357
		// Get the embedded task information.
358
		$sub = $this->getEmbeddedTask();
359
		// OL saves the task related properties in the embedded message
360
		$subProps = mapi_getprops($sub, [$this->props["taskupdates"]]);
361
362
		// If task is updated in task folder then we don't need to process
363
		// old response
364
		if ($this->isTaskRequestUpdated()) {
365
			return true;
366
		}
367
368
		$isReceivedItem = $this->isReceivedItem($messageProps);
369
370
		$taskHistory = 0;
371
		$taskState = 0;
372
		$taskAcceptanceState = 0;
373
		$taskOwner = 0;
374
		$isCreateAssociatedTask = false;
375
		$isAllowUpdateAssociatedTask = $subProps[$this->props["taskupdates"]];
376
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
377
378
		// Set correct taskmode and taskhistory depending on response type
379
		switch ($props[PR_MESSAGE_CLASS]) {
380
			case 'IPM.TaskRequest.Accept':
381
				$taskHistory = thAccepted;
382
				$taskState = $isReceivedItem ? tdsACC : tdsOWN;
383
				$taskOwner = $isReceivedItem ? olDelegatedTask : olOwnTask;
384
				$taskAcceptanceState = $isReceivedItem ? olTaskDelegationAccepted : olTaskNotDelegated;
385
				break;
386
387
			case 'IPM.TaskRequest.Decline':
388
				$isCreateAssociatedTask = $isReceivedItem;
389
				$isAllowUpdateAssociatedTask = $isReceivedItem;
390
				$taskHistory = thDeclined;
391
				$taskState = $isReceivedItem ? tdsDEC : tdsACC;
392
				$taskOwner = $isReceivedItem ? olOwnTask : olDelegatedTask;
393
				$taskAcceptanceState = $isReceivedItem ? olTaskDelegationDeclined : olTaskDelegationUnknown;
394
				break;
395
396
			case 'IPM.TaskRequest.Update':
397
			case 'IPM.TaskRequest.Complete':
398
				$taskHistory = thUpdated;
399
				$taskState = $isReceivedItem ? tdsACC : tdsOWN;
400
				$taskAcceptanceState = olTaskNotDelegated;
401
				$taskOwner = $isReceivedItem ? olDelegatedTask : olOwnTask;
402
				break;
403
		}
404
405
		$props = [
406
			$this->props['taskhistory'] => $taskHistory,
407
			$this->props['taskstate'] => $taskState,
408
			$this->props['task_acceptance_state'] => $taskAcceptanceState,
409
			$this->props['ownership'] => $taskOwner,
410
		];
411
412
		// Get the task for this response
413
		$task = $this->getAssociatedTask($isCreateAssociatedTask);
414
		if ($task && $isAllowUpdateAssociatedTask) {
415
			// To avoid duplication of attachments in associated task. we simple remove the
416
			// all attachments from associated task.
417
			$taskAttachTable = mapi_message_getattachmenttable($task);
418
			$taskAttachments = mapi_table_queryallrows($taskAttachTable, [PR_ATTACH_NUM]);
419
			foreach ($taskAttachments as $taskAttach) {
420
				mapi_message_deleteattach($task, $taskAttach[PR_ATTACH_NUM]);
421
			}
422
423
			$ignoreProps = [
424
				$this->props['taskstate'],
425
				$this->props['taskhistory'],
426
				$this->props['taskmode'],
427
				$this->props['taskfcreator'],
428
			];
429
			// Ignore PR_ICON_INDEX when task request response
430
			// is not received item.
431
			if ($isReceivedItem === false) {
432
				$ignoreProps[] = PR_ICON_INDEX;
433
			}
434
435
			// We copy all properties except taskstate, taskhistory, taskmode and taskfcreator properties
436
			// from $sub message to $task even also we copy all attachments from $sub to $task message.
437
			mapi_copyto($sub, [], $ignoreProps, $task);
438
			$senderProps = mapi_getprops($this->message, [
439
				PR_SENDER_NAME,
440
				PR_SENDER_EMAIL_ADDRESS,
441
				PR_SENDER_ENTRYID,
442
				PR_SENDER_ADDRTYPE,
443
				PR_SENDER_SEARCH_KEY,
444
				PR_MESSAGE_DELIVERY_TIME,
445
				PR_SENT_REPRESENTING_NAME,
446
				PR_SENT_REPRESENTING_EMAIL_ADDRESS,
447
				PR_SENT_REPRESENTING_ADDRTYPE,
448
				PR_SENT_REPRESENTING_ENTRYID,
449
				PR_SENT_REPRESENTING_SEARCH_KEY, ]);
450
451
			mapi_setprops($task, $senderProps);
452
453
			// Update taskstate and task history (last action done by the assignee)
454
			mapi_setprops($task, $props);
455
456
			// Copy missing properties from embedded task
457
			$subProperties = $this->getSubProperties();
458
			$subprops = mapi_getprops($sub, $subProperties);
459
			mapi_setprops($task, $subprops);
460
461
			mapi_savechanges($task);
462
		}
463
464
		mapi_setprops($this->message, $props);
465
		mapi_savechanges($this->message);
466
467
		if ($isReceivedItem) {
468
			$this->updateSentTaskRequest();
469
		}
470
471
		return true;
472
	}
473
474
	/**
475
	 * Update the sent task request in sent items folder.
476
	 */
477
	public function updateSentTaskRequest(): bool {
478
		$props = mapi_getprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

478
		$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
479
			$this->props['taskhistory'],
480
			$this->props["taskstate"],
481
			$this->props["ownership"],
482
			$this->props['task_goid'],
483
			$this->props['task_acceptance_state'],
484
			$this->props["tasklastuser"],
485
			$this->props["tasklastdelegate"], ]);
486
487
		$store = $this->getDefaultStore();
488
		$storeProps = mapi_getprops($store, [PR_IPM_SENTMAIL_ENTRYID]);
489
490
		$sentFolder = mapi_msgstore_openentry($store, $storeProps[PR_IPM_SENTMAIL_ENTRYID]);
491
		if (!$sentFolder) {
0 ignored issues
show
introduced by
$sentFolder is of type resource, thus it always evaluated to true.
Loading history...
492
			return false;
493
		}
494
495
		// Find the task by looking for the task_goid
496
		$restriction = [
497
			RES_PROPERTY,
498
			[
499
				RELOP => RELOP_EQ,
500
				ULPROPTAG => $this->props['task_goid'],
501
				VALUE => $props[$this->props['task_goid']],
502
			],
503
		];
504
505
		$contentsTable = mapi_folder_getcontentstable($sentFolder);
506
507
		$rows = mapi_table_queryallrows($contentsTable, [PR_ENTRYID], $restriction);
508
509
		if (!empty($rows)) {
510
			foreach ($rows as $row) {
511
				$sentTaskRequest = mapi_msgstore_openentry($store, $row[PR_ENTRYID]);
512
				mapi_setprops($sentTaskRequest, $props);
513
				mapi_setprops($sentTaskRequest, [PR_PROCESSED => true]);
514
				mapi_savechanges($sentTaskRequest);
515
			}
516
		}
517
518
		return true;
519
	}
520
521
	/**
522
	 * Creates a new message in the current user's outbox and submits it.
523
	 *
524
	 * Takes the task passed in the constructor as the task to be sent; recipient should
525
	 * be pre-existing. The task request will be sent to all recipients.
526
	 *
527
	 * @return true
528
	 */
529
	public function sendTaskRequest(string $prefix): bool {
530
		// Check if the task is in a public folder (which is not mail-enabled)
531
		$taskFolderStore = $this->getTaskFolderStore();
532
		$storeType = mapi_getprops($taskFolderStore, [PR_MDB_PROVIDER]);
533
		if (isset($storeType[PR_MDB_PROVIDER]) && $storeType[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
534
			throw new MAPIException(_("Cannot assign tasks in public folders. Public folders are not mail-enabled and cannot send task assignment notifications."), MAPI_E_NO_SUPPORT);
535
		}
536
537
		// Generate a TaskGlobalObjectId
538
		$taskid = $this->createTGOID();
539
		$messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

539
		$messageprops = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_SUBJECT]);
Loading history...
540
541
		// Set properties on Task Request
542
		mapi_setprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

542
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
543
			$this->props['task_goid'] => $taskid, // our new task_goid
544
			$this->props['taskstate'] => tdsACC, // state for our outgoing request
545
			$this->props['taskmode'] => tdmtNothing, // we're not sending a change
546
			$this->props['updatecount'] => 2, // version 2 (no idea)
547
			$this->props['task_acceptance_state'] => olTaskDelegationUnknown, // no reply yet
548
			$this->props['ownership'] => olDelegatedTask, // Task has been assigned
549
			$this->props['taskhistory'] => thAssigned, // Task has been assigned
550
			PR_CONVERSATION_TOPIC => $messageprops[PR_SUBJECT],
551
			PR_ICON_INDEX => ICON_TASK_ASSIGNER,
552
		]);
553
		$this->setLastUser();
554
		$this->setOwnerForAssignor();
555
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

555
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
556
557
		// Create outgoing task request message
558
		$outgoing = $this->createOutgoingMessage();
559
560
		// No need to copy PR_ICON_INDEX and  PR_SENT_* information in to outgoing message.
561
		$ignoreProps = [PR_ICON_INDEX, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY];
562
		mapi_copyto($this->message, [], $ignoreProps, $outgoing);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $src of mapi_copyto(). ( Ignorable by Annotation )

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

562
		mapi_copyto(/** @scrutinizer ignore-type */ $this->message, [], $ignoreProps, $outgoing);
Loading history...
563
564
		// Make it a task request, and put it in sent items after it is sent
565
		mapi_setprops($outgoing, [
566
			PR_MESSAGE_CLASS => "IPM.TaskRequest", // class is task request
567
			$this->props['taskstate'] => tdsOWN, // for the recipient he is the task owner
568
			$this->props['taskmode'] => tdmtTaskReq, // for the recipient it's a request
569
			$this->props['updatecount'] => 1, // version 2 is in the attachment
570
			PR_SUBJECT_PREFIX => $prefix,
571
			PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
572
		]);
573
574
		$attach = mapi_message_createattach($outgoing);
575
		mapi_setprops($attach, [
576
			PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG,
577
			PR_ATTACHMENT_HIDDEN => true,
578
			PR_DISPLAY_NAME => $messageprops[PR_SUBJECT], ]);
579
580
		$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
0 ignored issues
show
Bug introduced by
The function mapi_attach_openproperty 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

580
		$sub = /** @scrutinizer ignore-call */ mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
Loading history...
581
582
		mapi_copyto($this->message, [], [], $sub);
583
		mapi_setprops($sub, [PR_MESSAGE_CLASS => 'IPM.Task']);
584
585
		mapi_savechanges($sub);
586
587
		mapi_savechanges($attach);
588
589
		mapi_savechanges($outgoing);
590
		mapi_message_submitmessage($outgoing);
591
592
		return true;
593
	}
594
595
	// Assignee functions (called by the assignee)
596
597
	/**
598
	 * Updates task version counter.
599
	 *
600
	 * Must be called before each update to increase counter.
601
	 */
602
	public function updateTaskRequest(): void {
603
		$messageprops = mapi_getprops($this->message, [$this->props['updatecount']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

603
		$messageprops = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['updatecount']]);
Loading history...
604
605
		$messageprops[$this->props['updatecount']] = ($messageprops[$this->props['updatecount']] ?? 0) + 1;
606
607
		mapi_setprops($this->message, $messageprops);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

607
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, $messageprops);
Loading history...
608
	}
609
610
	/**
611
	 * Processes a task request.
612
	 *
613
	 * Message passed should be an IPM.TaskRequest message. The task request is then processed to create
614
	 * the task in the tasks folder if needed.
615
	 */
616
	public function processTaskRequest(): bool {
617
		if (!$this->isTaskRequest()) {
618
			return false;
619
		}
620
		$messageProps = mapi_getprops($this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

620
		$messageProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
Loading history...
621
		if (isset($messageProps[PR_PROCESSED]) && $messageProps[PR_PROCESSED]) {
622
			return true;
623
		}
624
625
		// if task is updated in task folder then we don't need to process
626
		// old request.
627
		if ($this->isTaskRequestUpdated()) {
628
			return true;
629
		}
630
631
		$isReceivedItem = $this->isReceivedItem($messageProps);
632
633
		$props = [];
634
		$props[PR_PROCESSED] = true;
635
		$props[$this->props["taskstate"]] = $isReceivedItem ? tdsOWN : tdsACC;
636
		$props[$this->props["ownership"]] = $isReceivedItem ? olOwnTask : olDelegatedTask;
637
638
		mapi_setprops($this->message, $props);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

638
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, $props);
Loading history...
639
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

639
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
640
641
		// Don't create associated task in task folder if "taskupdates" is not true.
642
		if (!$isReceivedItem && !$messageProps[$this->props["taskupdates"]]) {
643
			return true;
644
		}
645
		// create an associated task in task folder while
646
		// reading/loading task request on client side.
647
		$task = $this->getAssociatedTask(true);
648
649
		$taskProps = mapi_getprops($task, [$this->props['taskmultrecips']]);
650
		$taskProps[$this->props["taskstate"]] = $isReceivedItem ? tdsOWN : tdsACC;
651
		$taskProps[$this->props["taskhistory"]] = thAssigned;
652
		$taskProps[$this->props["taskmode"]] = tdmtNothing;
653
		$taskProps[$this->props["taskaccepted"]] = false;
654
		$taskProps[$this->props["taskfcreator"]] = false;
655
		$taskProps[$this->props["ownership"]] = $isReceivedItem ? olOwnTask : olDelegatedTask;
656
		$taskProps[$this->props["task_acceptance_state"]] = olTaskNotDelegated;
657
		$taskProps[PR_ICON_INDEX] = ICON_TASK_ASSIGNEE;
658
659
		mapi_setprops($task, $taskProps);
660
		$this->setAssignorInRecipients($task);
661
662
		mapi_savechanges($task);
663
664
		return true;
665
	}
666
667
	/**
668
	 * Accepts a task request and sends the response.
669
	 *
670
	 * Message passed should be an IPM.Task (eg the task from getAssociatedTask())
671
	 *
672
	 * Copies the task to the user's task folder, sets it to accepted, and sends the acceptation
673
	 * message back to the organizer. The caller is responsible for removing the message.
674
	 *
675
	 * @return array|false PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the task
676
	 */
677
	public function doAccept(): array|false {
678
		$prefix = _("Task Accepted:") . " ";
679
		$messageProps = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['taskstate']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

679
		$messageProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_CLASS, $this->props['taskstate']]);
Loading history...
680
681
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
682
			// Can only accept assignee task
683
			return false;
684
		}
685
686
		$this->setLastUser();
687
		$this->updateTaskRequest();
688
689
		$props = [
690
			$this->props['taskhistory'] => thAccepted,
691
			$this->props['task_assigned_time'] => time(),
692
			$this->props['taskaccepted'] => true,
693
			$this->props['task_acceptance_state'] => olTaskNotDelegated, ];
694
695
		// Message is TaskRequest then update the associated task as well.
696
		if ($this->isTaskRequest($messageProps[PR_MESSAGE_CLASS])) {
697
			$task = $this->getAssociatedTask(false);
698
			if ($task) {
699
				mapi_setprops($task, $props);
700
				mapi_savechanges($task);
701
			}
702
		}
703
704
		// Set as accepted
705
		mapi_setprops($this->message, $props);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

705
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, $props);
Loading history...
706
707
		// As we copy the all properties from received message we need to remove following
708
		// properties from accept response.
709
		mapi_deleteprops($this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_deleteprops(). ( Ignorable by Annotation )

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

709
		mapi_deleteprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
Loading history...
710
711
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

711
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
712
713
		$this->sendResponse(tdmtTaskAcc, $prefix);
714
715
		return $this->deleteReceivedTR();
716
	}
717
718
	/**
719
	 * Declines a task request and sends the response.
720
	 *
721
	 * Passed message must be a task request message, ie isTaskRequest() must return TRUE.
722
	 *
723
	 * Sends the decline message back to the organizer. The caller is responsible for removing the message.
724
	 *
725
	 * @return array|false TRUE on success, FALSE on failure
726
	 */
727
	public function doDecline(): array|false {
728
		$prefix = _("Task Declined:") . " ";
729
		$messageProps = mapi_getprops($this->message, [$this->props['taskstate']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

729
		$messageProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['taskstate']]);
Loading history...
730
731
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
732
			return false; // Can only decline assignee task
733
		}
734
735
		$this->setLastUser();
736
		$this->updateTaskRequest();
737
738
		// Set as declined
739
		mapi_setprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

739
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
740
			$this->props['taskhistory'] => thDeclined,
741
			$this->props['task_acceptance_state'] => olTaskDelegationDeclined,
742
		]);
743
		mapi_deleteprops($this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_deleteprops(). ( Ignorable by Annotation )

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

743
		mapi_deleteprops(/** @scrutinizer ignore-type */ $this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
Loading history...
744
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

744
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
745
746
		$this->sendResponse(tdmtTaskDec, $prefix);
747
748
		// Delete the associated task when task request is declined by the assignee.
749
		$task = $this->getAssociatedTask(false);
750
		if ($task) {
751
			$taskFolder = $this->getDefaultTasksFolder();
752
			$props = mapi_getprops($task, [PR_ENTRYID]);
753
			mapi_folder_deletemessages($taskFolder, [$props[PR_ENTRYID]]);
754
		}
755
756
		return $this->deleteReceivedTR();
757
	}
758
759
	/**
760
	 * Sends an update of the task if requested, and sends the Status-On-Completion report if complete and requested.
761
	 *
762
	 * If no updates were requested from the organizer, this function does nothing.
763
	 *
764
	 * @return bool TRUE if the update succeeded, FALSE otherwise
765
	 */
766
	public function doUpdate(): bool {
767
		$messageProps = mapi_getprops($this->message, [$this->props['taskstate'], PR_SUBJECT]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

767
		$messageProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['taskstate'], PR_SUBJECT]);
Loading history...
768
769
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
770
			return false; // Can only update assignee task
771
		}
772
773
		$this->setLastUser();
774
		$this->updateTaskRequest();
775
776
		// Set as updated
777
		mapi_setprops($this->message, [$this->props['taskhistory'] => thUpdated]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

777
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['taskhistory'] => thUpdated]);
Loading history...
778
779
		mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

779
		mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
780
781
		$props = mapi_getprops($this->message, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['recurring'], $this->props['complete']]);
782
		if (!$props[$this->props['complete']] && $props[$this->props['taskupdates']] && !(isset($props[$this->props['recurring']]) && $props[$this->props['recurring']])) {
783
			$this->sendResponse(tdmtTaskUpd, _("Task Updated:") . " ");
784
		}
785
		elseif ($props[$this->props['complete']]) {
786
			$this->sendResponse(tdmtTaskUpd, _("Task Completed:") . " ");
787
		}
788
789
		return true;
790
	}
791
792
	/**
793
	 * Gets the store associated with the task.
794
	 *
795
	 * Normally this will just open the store that the processed message is in. However, if the message is opened
796
	 * by a delegate, this function opens the store that the message was delegated from.
797
	 */
798
	public function getTaskFolderStore(): mixed {
799
		$ownerentryid = false;
800
801
		$rcvdprops = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

801
		$rcvdprops = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
Loading history...
802
		if (isset($rcvdprops[PR_RCVD_REPRESENTING_ENTRYID])) {
803
			$ownerentryid = $rcvdprops[PR_RCVD_REPRESENTING_ENTRYID];
804
		}
805
806
		if (!$ownerentryid) {
807
			$store = $this->store;
808
		}
809
		else {
810
			$ab = mapi_openaddressbook($this->session);
0 ignored issues
show
Bug introduced by
$this->session of type resource is incompatible with the type resource expected by parameter $session of mapi_openaddressbook(). ( Ignorable by Annotation )

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

810
			$ab = mapi_openaddressbook(/** @scrutinizer ignore-type */ $this->session);
Loading history...
811
			if (!$ab) {
0 ignored issues
show
introduced by
$ab is of type resource, thus it always evaluated to true.
Loading history...
812
				return false;
813
			}
814
815
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
816
			if (!$mailuser) {
0 ignored issues
show
introduced by
$mailuser is of type resource, thus it always evaluated to true.
Loading history...
817
				return false;
818
			}
819
820
			$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
821
			if (!isset($mailuserprops[PR_EMAIL_ADDRESS])) {
822
				return false;
823
			}
824
825
			$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
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_createentryid(). ( Ignorable by Annotation )

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

825
			$storeid = mapi_msgstore_createentryid(/** @scrutinizer ignore-type */ $this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
Loading history...
826
827
			$store = mapi_openmsgstore($this->session, $storeid);
0 ignored issues
show
Bug introduced by
$this->session of type resource is incompatible with the type resource expected by parameter $ses of mapi_openmsgstore(). ( Ignorable by Annotation )

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

827
			$store = mapi_openmsgstore(/** @scrutinizer ignore-type */ $this->session, $storeid);
Loading history...
828
		}
829
830
		return $store;
831
	}
832
833
	/**
834
	 * Opens the default task folder for the current user, or the specified user if passed.
835
	 */
836
	public function getDefaultTasksFolder(): mixed {
837
		$store = $this->getTaskFolderStore();
838
839
		$inbox = mapi_msgstore_getreceivefolder($store);
840
		$inboxprops = mapi_getprops($inbox, [PR_IPM_TASK_ENTRYID]);
841
		if (!isset($inboxprops[PR_IPM_TASK_ENTRYID])) {
842
			return false;
843
		}
844
845
		return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_TASK_ENTRYID]);
846
	}
847
848
	/**
849
	 * Prepares the sent representing properties from given MAPI store.
850
	 *
851
	 * @param mixed $store MAPI store object
852
	 *
853
	 * @return array[][][][][]|false if store is not mail box owner entryid then return false else prepare the sent representing props and return it
854
	 *
855
	 * @psalm-return array<array<array<array<array<array<never, never>>>>>>|false
856
	 */
857
	public function getSentReprProps(mixed $store): array|false {
858
		$storeprops = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
859
		if (!isset($storeprops[PR_MAILBOX_OWNER_ENTRYID])) {
860
			return false;
861
		}
862
863
		$ab = mapi_openaddressbook($this->session);
0 ignored issues
show
Bug introduced by
$this->session of type resource is incompatible with the type resource expected by parameter $session of mapi_openaddressbook(). ( Ignorable by Annotation )

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

863
		$ab = mapi_openaddressbook(/** @scrutinizer ignore-type */ $this->session);
Loading history...
864
		$mailuser = mapi_ab_openentry($ab, $storeprops[PR_MAILBOX_OWNER_ENTRYID]);
865
		$mailuserprops = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_ENTRYID]);
866
867
		$props = [];
868
		$props[PR_SENT_REPRESENTING_ADDRTYPE] = $mailuserprops[PR_ADDRTYPE];
869
		$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $mailuserprops[PR_EMAIL_ADDRESS];
870
		$props[PR_SENT_REPRESENTING_NAME] = $mailuserprops[PR_DISPLAY_NAME];
871
		$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $mailuserprops[PR_SEARCH_KEY];
872
		$props[PR_SENT_REPRESENTING_ENTRYID] = $mailuserprops[PR_ENTRYID];
873
874
		return $props;
875
	}
876
877
	/**
878
	 * Creates an outgoing message based on the passed message - will set delegate information
879
	 * and sent mail folder.
880
	 */
881
	public function createOutgoingMessage(): mixed {
882
		// Open our default store for this user (that's the only store we can submit in)
883
		$store = $this->getDefaultStore();
884
		$storeprops = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID]);
885
886
		$outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
887
		if (!$outbox) {
0 ignored issues
show
introduced by
$outbox is of type resource, thus it always evaluated to true.
Loading history...
888
			return false;
889
		}
890
891
		$outgoing = mapi_folder_createmessage($outbox);
892
		if (!$outgoing) {
0 ignored issues
show
introduced by
$outgoing is of type resource, thus it always evaluated to true.
Loading history...
893
			return false;
894
		}
895
896
		// Set SENT_REPRESENTING in case we're sending as a delegate
897
		$ownerstore = $this->getTaskFolderStore();
898
		$sentreprprops = $this->getSentReprProps($ownerstore);
899
900
		// Check if getSentReprProps returned false (e.g., for public folders which are not mail-enabled)
901
		if ($sentreprprops !== false) {
902
			mapi_setprops($outgoing, $sentreprprops);
903
		}
904
905
		mapi_setprops($outgoing, [PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID]]);
906
907
		return $outgoing;
908
	}
909
910
	/**
911
	 * Sends a response message (from assignee back to organizer).
912
	 *
913
	 * @param int $type Type of response (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd)
914
	 *
915
	 * @return bool TRUE on success
916
	 */
917
	public function sendResponse(int $type, mixed $prefix): bool {
918
		// Create a message in our outbox
919
		$outgoing = $this->createOutgoingMessage();
920
		$messageprops = mapi_getprops($this->message, [PR_CONVERSATION_TOPIC, PR_MESSAGE_CLASS, $this->props['complete']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

920
		$messageprops = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [PR_CONVERSATION_TOPIC, PR_MESSAGE_CLASS, $this->props['complete']]);
Loading history...
921
922
		$attach = mapi_message_createattach($outgoing);
923
		mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_CONVERSATION_TOPIC], PR_ATTACHMENT_HIDDEN => true]);
924
		$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
Bug introduced by
The function mapi_attach_openproperty 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

924
		$sub = /** @scrutinizer ignore-call */ mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
Loading history...
925
926
		$message = !$this->isTaskRequest() ? $this->message : $this->getAssociatedTask(false);
927
928
		$ignoreProps = [PR_ICON_INDEX, $this->props["categories"], PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY];
929
930
		mapi_copyto($message, [], $ignoreProps, $outgoing);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type resource; however, parameter $src of mapi_copyto() does only seem to accept resource, 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

930
		mapi_copyto(/** @scrutinizer ignore-type */ $message, [], $ignoreProps, $outgoing);
Loading history...
931
		mapi_copyto($message, [], [], $sub);
932
933
		if (!$this->setRecipientsForResponse($outgoing, $type)) {
934
			return false;
935
		}
936
937
		$props = [];
938
939
		switch ($type) {
940
			case tdmtTaskAcc:
941
				$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Accept";
942
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_ASSIGNER]);
943
				break;
944
945
			case tdmtTaskDec:
946
				$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Decline";
947
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_DECLINE]);
948
				break;
949
950
			case tdmtTaskUpd:
951
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_ASSIGNER]);
952
				if ($messageprops[$this->props['complete']]) {
953
					$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Complete";
954
				}
955
				else {
956
					$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Update";
957
				}
958
959
				break;
960
		}
961
962
		mapi_savechanges($sub);
963
		mapi_savechanges($attach);
964
965
		$props[PR_SUBJECT] = $prefix . $messageprops[PR_CONVERSATION_TOPIC];
966
		$props[$this->props['taskmode']] = $type;
967
		$props[$this->props['task_assigned_time']] = time();
968
969
		mapi_setprops($outgoing, $props);
970
971
		// taskCommentsInfo contains some comments which added by assignee while
972
		// edit response before sending task response.
973
		if ($this->taskCommentsInfo != '') {
974
			$comments = $this->getTaskCommentsInfo();
975
			$stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, STGM_TRANSACTED, MAPI_CREATE | MAPI_MODIFY);
976
			mapi_stream_setsize($stream, strlen($comments));
977
			mapi_stream_write($stream, $comments);
978
			mapi_stream_commit($stream);
979
		}
980
981
		mapi_savechanges($outgoing);
982
		mapi_message_submitmessage($outgoing);
983
984
		return true;
985
	}
986
987
	public function getDefaultStore(): mixed {
988
		$table = mapi_getmsgstorestable($this->session);
0 ignored issues
show
Bug introduced by
$this->session of type resource is incompatible with the type resource expected by parameter $session of mapi_getmsgstorestable(). ( Ignorable by Annotation )

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

988
		$table = mapi_getmsgstorestable(/** @scrutinizer ignore-type */ $this->session);
Loading history...
989
		$rows = mapi_table_queryallrows($table, [PR_DEFAULT_STORE, PR_ENTRYID]);
990
991
		foreach ($rows as $row) {
992
			if ($row[PR_DEFAULT_STORE]) {
993
				return mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
0 ignored issues
show
Bug introduced by
$this->session of type resource is incompatible with the type resource expected by parameter $ses of mapi_openmsgstore(). ( Ignorable by Annotation )

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

993
				return mapi_openmsgstore(/** @scrutinizer ignore-type */ $this->session, $row[PR_ENTRYID]);
Loading history...
994
			}
995
		}
996
997
		return false;
998
	}
999
1000
	/**
1001
	 * Creates a new TaskGlobalObjId.
1002
	 *
1003
	 * Just 16 bytes of random data
1004
	 */
1005
	public function createTGOID(): string {
1006
		$goid = "";
1007
		for ($i = 0; $i < 16; ++$i) {
1008
			$goid .= chr(random_int(0, 255));
1009
		}
1010
1011
		return $goid;
1012
	}
1013
1014
	/**
1015
	 * Gets the embedded task of task request. Further used to
1016
	 * create/update associated task of assigner/assignee.
1017
	 *
1018
	 * @return bool|resource embedded task if found else false
1019
	 */
1020
	public function getEmbeddedTask(): mixed {
1021
		$task = false;
1022
		$goid = mapi_getprops($this->message, [$this->props["task_goid"]]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

1022
		$goid = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props["task_goid"]]);
Loading history...
1023
		$attachmentTable = mapi_message_getattachmenttable($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $msg of mapi_message_getattachmenttable(). ( Ignorable by Annotation )

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

1023
		$attachmentTable = mapi_message_getattachmenttable(/** @scrutinizer ignore-type */ $this->message);
Loading history...
1024
		$restriction = [RES_PROPERTY,
1025
			[RELOP => RELOP_EQ,
1026
				ULPROPTAG => PR_ATTACH_METHOD,
1027
				VALUE => ATTACH_EMBEDDED_MSG, ],
1028
		];
1029
		$rows = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM], $restriction);
1030
1031
		if (empty($rows)) {
1032
			return $task;
1033
		}
1034
1035
		foreach ($rows as $row) {
1036
			try {
1037
				$attach = mapi_message_openattach($this->message, $row[PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $msg of mapi_message_openattach(). ( Ignorable by Annotation )

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

1037
				$attach = mapi_message_openattach(/** @scrutinizer ignore-type */ $this->message, $row[PR_ATTACH_NUM]);
Loading history...
1038
				$task = mapi_attach_openobj($attach);
1039
			}
1040
			catch (MAPIException) {
1041
				continue;
1042
			}
1043
1044
			$taskGoid = mapi_getprops($task, [$this->props["task_goid"]]);
1045
			if ($goid[$this->props["task_goid"]] === $taskGoid[$this->props["task_goid"]]) {
1046
				mapi_setprops($attach, [PR_ATTACHMENT_HIDDEN => true]);
1047
				mapi_savechanges($attach);
1048
				mapi_savechanges($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_savechanges(). ( Ignorable by Annotation )

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

1048
				mapi_savechanges(/** @scrutinizer ignore-type */ $this->message);
Loading history...
1049
				break;
1050
			}
1051
		}
1052
1053
		return $task;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $task also could return the type resource which is incompatible with the documented return type boolean|resource.
Loading history...
1054
	}
1055
1056
	/**
1057
	 * Sets the user name who has last used this task. Update the
1058
	 * tasklastdelegate and task_assigned_time.
1059
	 */
1060
	public function setLastUser(): void {
1061
		$delegatestore = $this->getDefaultStore();
1062
		$taskstore = $this->getTaskFolderStore();
1063
1064
		$delegateprops = mapi_getprops($delegatestore, [PR_MAILBOX_OWNER_NAME]);
1065
		$taskprops = mapi_getprops($taskstore, [PR_MAILBOX_OWNER_NAME]);
1066
1067
		// The owner of the task
1068
		$username = $delegateprops[PR_MAILBOX_OWNER_NAME];
1069
		// This is me (the one calling the script)
1070
		$delegate = $taskprops[PR_MAILBOX_OWNER_NAME];
1071
1072
		if ($this->isTaskRequest()) {
1073
			$task = $this->getAssociatedTask(false);
1074
			mapi_setprops($task, [
1075
				$this->props["tasklastuser"] => $username,
1076
				$this->props["tasklastdelegate"] => $delegate,
1077
				$this->props['task_assigned_time'] => time(),
1078
			]);
1079
			mapi_savechanges($task);
1080
		}
1081
		mapi_setprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

1081
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
1082
			$this->props["tasklastuser"] => $username,
1083
			$this->props["tasklastdelegate"] => $delegate,
1084
			$this->props['task_assigned_time'] => time(),
1085
		]);
1086
	}
1087
1088
	/**
1089
	 * Sets assignee as owner in the assignor's copy of task.
1090
	 * Assignee becomes the owner when a user/assignor assigns any task to someone.
1091
	 * There can be more than one assignee.
1092
	 */
1093
	public function setOwnerForAssignor(): void {
1094
		$recipTable = mapi_message_getrecipienttable($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $msg of mapi_message_getrecipienttable(). ( Ignorable by Annotation )

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

1094
		$recipTable = mapi_message_getrecipienttable(/** @scrutinizer ignore-type */ $this->message);
Loading history...
1095
		$recips = mapi_table_queryallrows($recipTable, [PR_DISPLAY_NAME]);
1096
1097
		if (!empty($recips)) {
1098
			$owner = [];
1099
			foreach ($recips as $value) {
1100
				$owner[] = $value[PR_DISPLAY_NAME];
1101
			}
1102
1103
			$props = [$this->props['owner'] => implode("; ", $owner)];
1104
			mapi_setprops($this->message, $props);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

1104
			mapi_setprops(/** @scrutinizer ignore-type */ $this->message, $props);
Loading history...
1105
		}
1106
	}
1107
1108
	/**
1109
	 * Sets assignor as recipients in assignee's copy of task.
1110
	 *
1111
	 * If assignor has requested task updates then the assignor is added as recipient type MAPI_CC.
1112
	 *
1113
	 * Also if assignor has request SOC then the assignor is also add as recipient type MAPI_BCC
1114
	 *
1115
	 * @param mixed $task assignee's copy of task
1116
	 */
1117
	public function setAssignorInRecipients(mixed $task): void {
1118
		$recipTable = mapi_message_getrecipienttable($task);
1119
1120
		// Delete all MAPI_TO recipients
1121
		$recips = mapi_table_queryallrows($recipTable, [PR_ROWID], [
1122
			RES_PROPERTY,
1123
			[
1124
				RELOP => RELOP_EQ,
1125
				ULPROPTAG => PR_RECIPIENT_TYPE,
1126
				VALUE => MAPI_TO,
1127
			],
1128
		]);
1129
		foreach ($recips as $recip) {
1130
			mapi_message_modifyrecipients($task, MODRECIP_REMOVE, [$recip]);
1131
		}
1132
1133
		$recips = [];
1134
		$taskReqProps = mapi_getprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

1134
		$taskReqProps = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
1135
			PR_SENT_REPRESENTING_NAME,
1136
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1137
			PR_SENT_REPRESENTING_ENTRYID,
1138
			PR_SENT_REPRESENTING_ADDRTYPE,
1139
			PR_SENT_REPRESENTING_SEARCH_KEY,
1140
		]);
1141
		$associatedTaskProps = mapi_getprops($task, [
1142
			$this->props['taskupdates'],
1143
			$this->props['tasksoc'],
1144
			$this->props['taskmultrecips'],
1145
		]);
1146
1147
		// Build assignor info
1148
		$assignor = [
1149
			PR_ENTRYID => $taskReqProps[PR_SENT_REPRESENTING_ENTRYID],
1150
			PR_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
1151
			PR_EMAIL_ADDRESS => $taskReqProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
1152
			PR_RECIPIENT_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
1153
			PR_ADDRTYPE => empty($taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE],
1154
			PR_RECIPIENT_FLAGS => recipSendable,
1155
			PR_SEARCH_KEY => $taskReqProps[PR_SENT_REPRESENTING_SEARCH_KEY],
1156
		];
1157
1158
		// Assignor has requested task updates, so set him/her as MAPI_CC in recipienttable.
1159
		if ((isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['taskupdates']]) &&
1160
			!(isset($associatedTaskProps[$this->props['taskmultrecips']]) && $associatedTaskProps[$this->props['taskmultrecips']] == tmrReceived)) {
1161
			$assignor[PR_RECIPIENT_TYPE] = MAPI_CC;
1162
			$recips[] = $assignor;
1163
		}
1164
1165
		// Assignor wants to receive an email report when task is mark as 'Complete', so in recipients as MAPI_BCC
1166
		if ($associatedTaskProps[$this->props['tasksoc']]) {
1167
			$assignor[PR_RECIPIENT_TYPE] = MAPI_BCC;
1168
			$recips[] = $assignor;
1169
		}
1170
1171
		if (!empty($recips)) {
1172
			mapi_message_modifyrecipients($task, MODRECIP_ADD, $recips);
1173
		}
1174
	}
1175
1176
	/**
1177
	 * Deletes incoming task request from Inbox.
1178
	 *
1179
	 * @returns array|bool PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the deleted task request
1180
	 */
1181
	public function deleteReceivedTR(): array|false {
1182
		$store = $this->getTaskFolderStore();
1183
		$storeType = mapi_getprops($store, [PR_MDB_PROVIDER]);
1184
		if ($storeType[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1185
			$store = $this->getDefaultStore();
1186
		}
1187
		$inbox = mapi_msgstore_getreceivefolder($store);
1188
1189
		$storeProps = mapi_getprops($store, [PR_IPM_WASTEBASKET_ENTRYID]);
1190
		$props = mapi_getprops($this->message, [$this->props['task_goid']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

1190
		$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['task_goid']]);
Loading history...
1191
		$goid = $props[$this->props['task_goid']];
1192
1193
		// Find the task by looking for the task_goid
1194
		$restriction = [
1195
			RES_PROPERTY,
1196
			[
1197
				RELOP => RELOP_EQ,
1198
				ULPROPTAG => $this->props['task_goid'],
1199
				VALUE => $goid,
1200
			],
1201
		];
1202
1203
		$contents = mapi_folder_getcontentstable($inbox);
1204
1205
		$rows = mapi_table_queryallrows($contents, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID], $restriction);
1206
1207
		if (!empty($rows)) {
1208
			// If there are multiple, just use the first
1209
			$entryid = $rows[0][PR_ENTRYID];
1210
			$wastebasket = mapi_msgstore_openentry($store, $storeProps[PR_IPM_WASTEBASKET_ENTRYID]);
1211
			mapi_folder_copymessages($inbox, [$entryid], $wastebasket, MESSAGE_MOVE);
1212
1213
			return [PR_ENTRYID => $entryid, PR_PARENT_ENTRYID => $rows[0][PR_PARENT_ENTRYID], PR_STORE_ENTRYID => $rows[0][PR_STORE_ENTRYID]];
1214
		}
1215
1216
		return false;
1217
	}
1218
1219
	/**
1220
	 * Sets recipients for the outgoing message according to type of the response.
1221
	 *
1222
	 * If it is a task update, then only recipient type MAPI_CC are taken from the task message.
1223
	 *
1224
	 * If it is accept/decline response, then PR_SENT_REPRESENTATING_XXXX are taken as recipient.
1225
	 *
1226
	 * @param mixed $outgoing     outgoing mapi message
1227
	 * @param int   $responseType response type (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd)
1228
	 */
1229
	public function setRecipientsForResponse(mixed $outgoing, int $responseType): bool {
1230
		// Clear recipients from outgoing msg
1231
		$this->deleteAllRecipients($outgoing);
1232
1233
		// If it is a task update then get MAPI_CC recipients which are assignors who has asked for task update.
1234
		if ($responseType == tdmtTaskUpd) {
1235
			$props = mapi_getprops($this->message, [$this->props['complete']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

1235
			$props = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['complete']]);
Loading history...
1236
			$isComplete = $props[$this->props['complete']];
1237
1238
			$recipTable = mapi_message_getrecipienttable($this->message);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $msg of mapi_message_getrecipienttable(). ( Ignorable by Annotation )

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

1238
			$recipTable = mapi_message_getrecipienttable(/** @scrutinizer ignore-type */ $this->message);
Loading history...
1239
			$recips = mapi_table_queryallrows($recipTable, $this->recipProps, [
1240
				RES_PROPERTY,
1241
				[
1242
					RELOP => RELOP_EQ,
1243
					ULPROPTAG => PR_RECIPIENT_TYPE,
1244
					VALUE => ($isComplete ? MAPI_BCC : MAPI_CC),
1245
				],
1246
			]);
1247
1248
			// No recipients found, return error
1249
			if (empty($recips)) {
1250
				return false;
1251
			}
1252
1253
			foreach ($recips as $recip) {
1254
				$recip[PR_RECIPIENT_TYPE] = MAPI_TO;	// Change recipient type to MAPI_TO
1255
				mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
1256
			}
1257
1258
			return true;
1259
		}
1260
1261
		$orgprops = mapi_getprops($this->message, [
1262
			PR_SENT_REPRESENTING_NAME,
1263
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1264
			PR_SENT_REPRESENTING_ADDRTYPE,
1265
			PR_SENT_REPRESENTING_ENTRYID,
1266
			PR_SUBJECT,
1267
		]);
1268
1269
		$recip = [
1270
			PR_DISPLAY_NAME => $orgprops[PR_SENT_REPRESENTING_NAME],
1271
			PR_EMAIL_ADDRESS => $orgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
1272
			PR_ADDRTYPE => $orgprops[PR_SENT_REPRESENTING_ADDRTYPE],
1273
			PR_ENTRYID => $orgprops[PR_SENT_REPRESENTING_ENTRYID],
1274
			PR_RECIPIENT_TYPE => MAPI_TO, ];
1275
1276
		mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
1277
1278
		return true;
1279
	}
1280
1281
	/**
1282
	 * Deletes all recipients from given message object.
1283
	 *
1284
	 * @param mixed $message MAPI message from which recipients are to be removed
1285
	 */
1286
	public function deleteAllRecipients(mixed $message): void {
1287
		$recipTable = mapi_message_getrecipienttable($message);
1288
		$recipRows = mapi_table_queryallrows($recipTable, [PR_ROWID]);
1289
1290
		foreach ($recipRows as $recipient) {
1291
			mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1292
		}
1293
	}
1294
1295
	/**
1296
	 * Marks the record to complete and send complete update
1297
	 * notification to assigner.
1298
	 *
1299
	 * @return bool TRUE if the update succeeded, FALSE otherwise
1300
	 */
1301
	public function sendCompleteUpdate(): bool {
1302
		$messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_getprops(). ( Ignorable by Annotation )

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

1302
		$messageprops = mapi_getprops(/** @scrutinizer ignore-type */ $this->message, [$this->props['taskstate']]);
Loading history...
1303
1304
		if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
1305
			return false; // Can only decline assignee task
1306
		}
1307
1308
		mapi_setprops($this->message, [
0 ignored issues
show
Bug introduced by
$this->message of type resource is incompatible with the type resource expected by parameter $any of mapi_setprops(). ( Ignorable by Annotation )

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

1308
		mapi_setprops(/** @scrutinizer ignore-type */ $this->message, [
Loading history...
1309
			$this->props['complete'] => true,
1310
			$this->props['date_completed'] => time(),
1311
			$this->props['status'] => 2,
1312
			$this->props['percent_complete'] => 1,
1313
		]);
1314
1315
		return $this->doUpdate();
1316
	}
1317
1318
	/**
1319
	 * Returns extra info about task request comments along with message body
1320
	 * which will be included in body while sending task request/response.
1321
	 *
1322
	 * @return string info about task request comments along with message body
1323
	 */
1324
	public function getTaskCommentsInfo(): string {
1325
		return $this->taskCommentsInfo;
1326
	}
1327
1328
	/**
1329
	 * Sets an extra info about task request comments along with message body
1330
	 * which will be included in body while sending task request/response.
1331
	 *
1332
	 * @param string $taskCommentsInfo info about task request comments along with message body
1333
	 */
1334
	public function setTaskCommentsInfo(string $taskCommentsInfo): void {
1335
		$this->taskCommentsInfo = $taskCommentsInfo;
1336
	}
1337
1338
	public function getSubProperties(): array {
1339
		$subProperties = [];
1340
		$subProperties["subject"] = PR_SUBJECT;
1341
		$subProperties["convtopic"] = PR_CONVERSATION_TOPIC;
1342
		$subProperties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete;
1343
		$subProperties["date_completed"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted;
1344
		$subProperties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
1345
		$subProperties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate;
1346
		$subProperties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate;
1347
		$subProperties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus;
1348
		$subProperties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete;
1349
		$subProperties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
1350
		$subProperties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
1351
		$subProperties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
1352
		$subProperties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
1353
		$subProperties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
1354
		$subProperties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
1355
1356
		return getPropIdsFromStrings($this->store, $subProperties);
1357
	}
1358
}
1359