Issues (207)

class.taskrequest.php (4 issues)

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
6
 */
7
8
/*
9
* In general
10
*
11
* This class never actually modifies a task item unless we receive a task request update. This means
12
* that setting all the properties to make the task item itself behave like a task request is up to the
13
* caller.
14
*
15
* The only exception to this is the generation of the TaskGlobalObjId, the unique identifier identifying
16
* this task request to both the organizer and the assignee. The globalobjectid is generated when the
17
* task request is sent via sendTaskRequest.
18
*/
19
20
/* The TaskMode value is only used for the IPM.TaskRequest items.
21
 * It must 0 (tdmtNothing) on IPM.Task items.
22
 *
23
 * It is used to indicate the type of change that is being
24
 * carried in the IPM.TaskRequest item (although this information seems
25
 * redundant due to that information already being available in PR_MESSAGE_CLASS).
26
 */
27
define('tdmtNothing', 0);			// Value in IPM.Task items
28
define('tdmtTaskReq', 1);			// Assigner -> Assignee
29
define('tdmtTaskAcc', 2);			// Assignee -> Assigner
30
define('tdmtTaskDec', 3);			// Assignee -> Assigner
31
define('tdmtTaskUpd', 4);			// Assignee -> Assigner
32
define('tdmtTaskSELF', 5);			// Assigner -> Assigner (?)
33
34
/* The TaskHistory is used to show the last action on the task
35
 * on both the assigner and the assignee's side.
36
 *
37
 * It is used in combination with 'task_assigned_time' and 'tasklastdelegate'
38
 * or 'tasklastuser' to show the information at the top of the task request in
39
 * the format 'Accepted by <user> on 01-01-2010 11:00'.
40
 */
41
define('thNone', 0);
42
define('thAccepted', 1);			// Set by assignee
43
define('thDeclined', 2);			// Set by assignee
44
define('thUpdated', 3);				// Set by assignee
45
define('thDueDateChanged', 4);
46
define('thAssigned', 5);			// Set by assigner
47
48
/* The TaskState value is used to differentiate the version of a task
49
 * in the assigner's folder and the version in the
50
 * assignee's folder. The buttons shown depend on this and
51
 * the 'taskaccepted' boolean (for the assignee)
52
 */
53
define('tdsNOM', 0);		// Got a response to a deleted task, and re-created the task for the assigner
54
define('tdsOWNNEW', 1);		// Not assigned
55
define('tdsOWN', 2);		// Assignee version
56
define('tdsACC', 3);		// Assigner version
57
define('tdsDEC', 4);		// Assigner version, but assignee declined
58
59
/* The TaskAcceptanceState is used for the assigner to indicate state */
60
define('olTaskNotDelegated', 0);
61
define('olTaskDelegationUnknown', 1); // After sending req
62
define('olTaskDelegationAccepted', 2); // After receiving accept
63
define('olTaskDelegationDeclined', 3); // After receiving decline
64
65
/* The task ownership indicates the role of the current user relative to the task. */
66
define('olNewTask', 0);
67
define('olDelegatedTask', 1);	// Task has been assigned
68
define('olOwnTask', 2);			// Task owned
69
70
/* taskmultrecips indicates whether the task request sent or received has multiple assignees or not. */
71
define('tmrNone', 0);
72
define('tmrSent', 1);		// Task has been sent to multiple assignee
73
define('tmrReceived', 2);	// Task Request received has multiple assignee
74
75
// Task icon index.
76
define('ICON_TASK_ASSIGNEE', 0x00000502);
77
define('ICON_TASK_DECLINE', 0x00000506);
78
define('ICON_TASK_ASSIGNER', 0x00000503);
79
80
class TaskRequest {
81
	private $props;
82
83
	/**
84
	 * @var resource
85
	 */
86
	private $store;
87
88
	/**
89
	 * @var resource
90
	 */
91
	private $message;
92
93
	/**
94
	 * @var resource
95
	 */
96
	private $session;
97
98
	/**
99
	 * @var string
100
	 */
101
	private $taskCommentsInfo;
102
103
	// All recipient properties
104
	public $recipProps = [
105
		PR_ENTRYID,
106
		PR_DISPLAY_NAME,
107
		PR_EMAIL_ADDRESS,
108
		PR_RECIPIENT_ENTRYID,
109
		PR_RECIPIENT_TYPE,
110
		PR_SEND_INTERNET_ENCODING,
111
		PR_SEND_RICH_INFO,
112
		PR_RECIPIENT_DISPLAY_NAME,
113
		PR_ADDRTYPE,
114
		PR_DISPLAY_TYPE,
115
		PR_RECIPIENT_TRACKSTATUS,
116
		PR_RECIPIENT_TRACKSTATUS_TIME,
117
		PR_RECIPIENT_FLAGS,
118
		PR_ROWID,
119
		PR_SEARCH_KEY,
120
	];
121
122
	/**
123
	 * Constructor.
124
	 *
125
	 * Constructs a TaskRequest object for the specified message. This can be either the task request
126
	 * message itself (in the inbox) or the task in the tasks folder, depending on the action to be performed.
127
	 *
128
	 * As a general rule, the object message passed is the object 'in view' when the user performs one of the
129
	 * actions in this class.
130
	 *
131
	 * @param resource $store   MAPI Store in which $message resides. This is also the store where the tasks folder is assumed to be in
132
	 * @param resource $message MAPI Message to which the task request refers (can be an email or a task)
133
	 * @param resource $session MAPI Session which is used to open tasks folders for delegated task requests or responses
134
	 */
135
	public function __construct($store, $message, $session) {
136
		$this->store = $store;
137
		$this->message = $message;
138
		$this->session = $session;
139
		$this->taskCommentsInfo = '';
140
141
		$properties = [];
142
		$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
143
		$properties["updatecount"] = "PT_LONG:PSETID_Task:0x8112";
144
		$properties["taskstate"] = "PT_LONG:PSETID_Task:0x8113";
145
		$properties["taskmultrecips"] = "PT_LONG:PSETID_Task:0x8120";
146
		$properties["taskupdates"] = "PT_BOOLEAN:PSETID_Task:0x811b";
147
		$properties["tasksoc"] = "PT_BOOLEAN:PSETID_Task:0x8119";
148
		$properties["taskhistory"] = "PT_LONG:PSETID_Task:0x811a";
149
		$properties["taskmode"] = "PT_LONG:PSETID_Common:0x8518";
150
		$properties["task_goid"] = "PT_BINARY:PSETID_Common:0x8519";
151
		$properties["complete"] = "PT_BOOLEAN:PSETID_Common:" . PidLidTaskComplete;
152
		$properties["task_assigned_time"] = "PT_SYSTIME:PSETID_Task:0x8115";
153
		$properties["taskfcreator"] = "PT_BOOLEAN:PSETID_Task:0x0x811e";
154
		$properties["tasklastuser"] = "PT_STRING8:PSETID_Task:0x8122";
155
		$properties["tasklastdelegate"] = "PT_STRING8:PSETID_Task:0x8125";
156
		$properties["taskaccepted"] = "PT_BOOLEAN:PSETID_Task:0x8108";
157
		$properties["task_acceptance_state"] = "PT_LONG:PSETID_Task:0x812a";
158
		$properties["ownership"] = "PT_LONG:PSETID_Task:0x8129";
159
160
		$properties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete;
161
		$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted;
162
		$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
163
		$properties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate;
164
		$properties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate;
165
		$properties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus;
166
		$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete;
167
		$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
168
		$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
169
		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
170
		$properties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
171
		$properties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
172
		$properties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
173
174
		$this->props = getPropIdsFromStrings($store, $properties);
175
	}
176
177
	// General functions
178
179
	/**
180
	 * Returns TRUE if the message pointed to is an incoming task request and should
181
	 * therefore be replied to with doAccept or doDecline().
182
	 *
183
	 * @param mixed $messageClass message class to use for checking
184
	 *
185
	 * @return bool true if this is a task request else false
186
	 */
187
	public function isTaskRequest($messageClass = false) {
188
		if ($messageClass === false) {
189
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
190
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
191
		}
192
193
		if ($messageClass !== false && $messageClass === "IPM.TaskRequest") {
194
			return true;
195
		}
196
197
		return false;
198
	}
199
200
	/**
201
	 * Returns TRUE if the message pointed to is a returning task request response.
202
	 *
203
	 * @param mixed $messageClass message class to use for checking
204
	 *
205
	 * @return bool true if this is a task request else false
206
	 */
207
	public function isTaskRequestResponse($messageClass = false) {
208
		if ($messageClass === false) {
209
			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
210
			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
211
		}
212
213
		if ($messageClass !== false && strpos($messageClass, "IPM.TaskRequest.") === 0) {
214
			return true;
215
		}
216
217
		return false;
218
	}
219
220
	/**
221
	 * Returns TRUE if the message pointed to is an incoming task request/response.
222
	 *
223
	 * @param array $props The MAPI properties to check message is an incoming task request/response
224
	 *
225
	 * @return bool true if this is an incoming task request/response else false
226
	 */
227
	public function isReceivedItem($props) {
228
		return isset($props[PR_MESSAGE_TO_ME]) ? $props[PR_MESSAGE_TO_ME] : false;
229
	}
230
231
	/**
232
	 * Gets the task associated with an IPM.TaskRequest message.
233
	 *
234
	 * If the task does not exist yet, it is created, using the attachment object in the
235
	 * task request item.
236
	 *
237
	 * @param bool $create true - try create task in user's task folder if task does not exist
238
	 *                     false - find the associated task in user's task folder
239
	 *
240
	 * @return bool|resource associated task of task request else false
241
	 */
242
	public function getAssociatedTask($create) {
243
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['task_goid']]);
244
245
		if ($props[PR_MESSAGE_CLASS] == "IPM.Task") {
246
			// Message itself is task, so return that
247
			return $this->message;
248
		}
249
250
		$taskFolder = $this->getDefaultTasksFolder();
251
		$goid = $props[$this->props['task_goid']];
252
253
		// Find the task by looking for the task_goid
254
		$restriction = [
255
			RES_PROPERTY,
256
			[
257
				RELOP => RELOP_EQ,
258
				ULPROPTAG => $this->props['task_goid'],
259
				VALUE => $goid,
260
			],
261
		];
262
263
		$contents = mapi_folder_getcontentstable($taskFolder);
264
265
		$rows = mapi_table_queryallrows($contents, [PR_ENTRYID], $restriction);
266
267
		if (empty($rows)) {
268
			// None found, create one if possible
269
			if (!$create) {
270
				return false;
271
			}
272
273
			$task = mapi_folder_createmessage($taskFolder);
274
275
			$sub = $this->getEmbeddedTask();
276
			mapi_copyto($sub, [], [$this->props['categories']], $task);
277
278
			$senderProps = [
279
				PR_SENT_REPRESENTING_NAME,
280
				PR_SENT_REPRESENTING_EMAIL_ADDRESS,
281
				PR_SENT_REPRESENTING_ENTRYID,
282
				PR_SENT_REPRESENTING_ADDRTYPE,
283
				PR_SENT_REPRESENTING_SEARCH_KEY,
284
				PR_SENDER_NAME,
285
				PR_SENDER_EMAIL_ADDRESS,
286
				PR_SENDER_ENTRYID,
287
				PR_SENDER_ADDRTYPE,
288
				PR_SENDER_SEARCH_KEY, ];
289
290
			// Copy sender information from the e-mail
291
			$props = mapi_getprops($this->message, $senderProps);
292
			$props[PR_MESSAGE_CLASS] = 'IPM.Task';
293
			mapi_setprops($task, $props);
294
		}
295
		else {
296
			// If there are multiple, just use the first
297
			$entryid = $rows[0][PR_ENTRYID];
298
299
			$store = $this->getTaskFolderStore();
300
			$task = mapi_msgstore_openentry($store, $entryid);
301
		}
302
303
		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...
304
	}
305
306
	/**
307
	 * Function which checks that if we have received a task request/response
308
	 * for an already updated task in task folder.
309
	 *
310
	 * @return bool true if task request is updated later
311
	 */
312
	public function isTaskRequestUpdated() {
313
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['task_goid'], $this->props['updatecount']]);
314
		$result = false;
315
		$associatedTask = $this->getAssociatedTask(false);
316
		if ($this->isTaskRequest($props[PR_MESSAGE_CLASS])) {
317
			if ($associatedTask) {
318
				return true;
319
			}
320
			$folder = $this->getDefaultTasksFolder();
321
			$goid = $props[$this->props['task_goid']];
322
323
			// Find the task by looking for the task_goid
324
			$restriction = [
325
				RES_PROPERTY,
326
				[
327
					RELOP => RELOP_EQ,
328
					ULPROPTAG => $this->props['task_goid'],
329
					VALUE => $goid,
330
				],
331
			];
332
333
			$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS | SHOW_SOFT_DELETES);
334
			$softDeletedItems = mapi_table_queryallrows($table, [PR_ENTRYID], $restriction);
335
			if (!empty($softDeletedItems)) {
336
				return true;
337
			}
338
		}
339
340
		if ($associatedTask !== false) {
341
			$taskItemProps = mapi_getprops($associatedTask, [$this->props['updatecount']]);
342
			/*
343
			 * if(message_counter < task_counter) task object is newer then task response (task is updated)
344
			 * if(message_counter >= task_counter) task is not updated, do normal processing
345
			 */
346
			if (isset($taskItemProps[$this->props['updatecount']], $props[$this->props['updatecount']])) {
347
				if ($props[$this->props['updatecount']] < $taskItemProps[$this->props['updatecount']]) {
348
					$result = true;
349
				}
350
			}
351
		}
352
353
		return $result;
354
	}
355
356
	// Organizer functions (called by the organizer)
357
358
	/**
359
	 * Processes a task request response, which can be any of the following:
360
	 * - Task accept (task history is marked as accepted)
361
	 * - Task decline (task history is marked as declined)
362
	 * - Task update (updates completion %, etc).
363
	 *
364
	 * @return true
365
	 */
366
	public function processTaskResponse(): bool {
367
		$messageProps = mapi_getprops($this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
368
		if (isset($messageProps[PR_PROCESSED]) && $messageProps[PR_PROCESSED]) {
369
			return true;
370
		}
371
		mapi_setprops($this->message, [PR_PROCESSED => true]);
372
		mapi_savechanges($this->message);
373
374
		// Get the embedded task information.
375
		$sub = $this->getEmbeddedTask();
376
		// OL saves the task related properties in the embedded message
377
		$subProps = mapi_getprops($sub, [$this->props["taskupdates"]]);
378
379
		// If task is updated in task folder then we don't need to process
380
		// old response
381
		if ($this->isTaskRequestUpdated()) {
382
			return true;
383
		}
384
385
		$isReceivedItem = $this->isReceivedItem($messageProps);
386
387
		$taskHistory = 0;
388
		$taskState = 0;
389
		$taskAcceptanceState = 0;
390
		$taskOwner = 0;
391
		$isCreateAssociatedTask = false;
392
		$isAllowUpdateAssociatedTask = $subProps[$this->props["taskupdates"]];
393
		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
394
395
		// Set correct taskmode and taskhistory depending on response type
396
		switch ($props[PR_MESSAGE_CLASS]) {
397
			case 'IPM.TaskRequest.Accept':
398
				$taskHistory = thAccepted;
399
				$taskState = $isReceivedItem ? tdsACC : tdsOWN;
400
				$taskOwner = $isReceivedItem ? olDelegatedTask : olOwnTask;
401
				$taskAcceptanceState = $isReceivedItem ? olTaskDelegationAccepted : olTaskNotDelegated;
402
				break;
403
404
			case 'IPM.TaskRequest.Decline':
405
				$isCreateAssociatedTask = $isReceivedItem;
406
				$isAllowUpdateAssociatedTask = $isReceivedItem;
407
				$taskHistory = thDeclined;
408
				$taskState = $isReceivedItem ? tdsDEC : tdsACC;
409
				$taskOwner = $isReceivedItem ? olOwnTask : olDelegatedTask;
410
				$taskAcceptanceState = $isReceivedItem ? olTaskDelegationDeclined : olTaskDelegationUnknown;
411
				break;
412
413
			case 'IPM.TaskRequest.Update':
414
			case 'IPM.TaskRequest.Complete':
415
				$taskHistory = thUpdated;
416
				$taskState = $isReceivedItem ? tdsACC : tdsOWN;
417
				$taskAcceptanceState = olTaskNotDelegated;
418
				$taskOwner = $isReceivedItem ? olDelegatedTask : olOwnTask;
419
				break;
420
		}
421
422
		$props = [
423
			$this->props['taskhistory'] => $taskHistory,
424
			$this->props['taskstate'] => $taskState,
425
			$this->props['task_acceptance_state'] => $taskAcceptanceState,
426
			$this->props['ownership'] => $taskOwner,
427
		];
428
429
		// Get the task for this response
430
		$task = $this->getAssociatedTask($isCreateAssociatedTask);
431
		if ($task && $isAllowUpdateAssociatedTask) {
432
			// To avoid duplication of attachments in associated task. we simple remove the
433
			// all attachments from associated task.
434
			$taskAttachTable = mapi_message_getattachmenttable($task);
435
			$taskAttachments = mapi_table_queryallrows($taskAttachTable, [PR_ATTACH_NUM]);
436
			foreach ($taskAttachments as $taskAttach) {
437
				mapi_message_deleteattach($task, $taskAttach[PR_ATTACH_NUM]);
438
			}
439
440
			$ignoreProps = [
441
				$this->props['taskstate'],
442
				$this->props['taskhistory'],
443
				$this->props['taskmode'],
444
				$this->props['taskfcreator'],
445
			];
446
			// Ignore PR_ICON_INDEX when task request response
447
			// is not received item.
448
			if ($isReceivedItem === false) {
449
				$ignoreProps[] = PR_ICON_INDEX;
450
			}
451
452
			// We copy all properties except taskstate, taskhistory, taskmode and taskfcreator properties
453
			// from $sub message to $task even also we copy all attachments from $sub to $task message.
454
			mapi_copyto($sub, [], $ignoreProps, $task);
455
			$senderProps = mapi_getprops($this->message, [
456
				PR_SENDER_NAME,
457
				PR_SENDER_EMAIL_ADDRESS,
458
				PR_SENDER_ENTRYID,
459
				PR_SENDER_ADDRTYPE,
460
				PR_SENDER_SEARCH_KEY,
461
				PR_MESSAGE_DELIVERY_TIME,
462
				PR_SENT_REPRESENTING_NAME,
463
				PR_SENT_REPRESENTING_EMAIL_ADDRESS,
464
				PR_SENT_REPRESENTING_ADDRTYPE,
465
				PR_SENT_REPRESENTING_ENTRYID,
466
				PR_SENT_REPRESENTING_SEARCH_KEY, ]);
467
468
			mapi_setprops($task, $senderProps);
469
470
			// Update taskstate and task history (last action done by the assignee)
471
			mapi_setprops($task, $props);
472
473
			// Copy missing properties from embedded task
474
			$subProperties = $this->getSubProperties();
475
			$subprops = mapi_getprops($sub, $subProperties);
476
			mapi_setprops($task, $subprops);
477
478
			mapi_savechanges($task);
479
		}
480
481
		mapi_setprops($this->message, $props);
482
		mapi_savechanges($this->message);
483
484
		if ($isReceivedItem) {
485
			$this->updateSentTaskRequest();
486
		}
487
488
		return true;
489
	}
490
491
	/**
492
	 * Update the sent task request in sent items folder.
493
	 *
494
	 * @return bool
495
	 */
496
	public function updateSentTaskRequest() {
497
		$props = mapi_getprops($this->message, [
498
			$this->props['taskhistory'],
499
			$this->props["taskstate"],
500
			$this->props["ownership"],
501
			$this->props['task_goid'],
502
			$this->props['task_acceptance_state'],
503
			$this->props["tasklastuser"],
504
			$this->props["tasklastdelegate"], ]);
505
506
		$store = $this->getDefaultStore();
507
		$storeProps = mapi_getprops($store, [PR_IPM_SENTMAIL_ENTRYID]);
508
509
		$sentFolder = mapi_msgstore_openentry($store, $storeProps[PR_IPM_SENTMAIL_ENTRYID]);
510
		if (!$sentFolder) {
511
			return false;
512
		}
513
514
		// Find the task by looking for the task_goid
515
		$restriction = [
516
			RES_PROPERTY,
517
			[
518
				RELOP => RELOP_EQ,
519
				ULPROPTAG => $this->props['task_goid'],
520
				VALUE => $props[$this->props['task_goid']],
521
			],
522
		];
523
524
		$contentsTable = mapi_folder_getcontentstable($sentFolder);
525
526
		$rows = mapi_table_queryallrows($contentsTable, [PR_ENTRYID], $restriction);
527
528
		if (!empty($rows)) {
529
			foreach ($rows as $row) {
530
				$sentTaskRequest = mapi_msgstore_openentry($store, $row[PR_ENTRYID]);
531
				mapi_setprops($sentTaskRequest, $props);
532
				mapi_setprops($sentTaskRequest, [PR_PROCESSED => true]);
533
				mapi_savechanges($sentTaskRequest);
534
			}
535
		}
536
537
		return true;
538
	}
539
540
	/**
541
	 * Creates a new message in the current user's outbox and submits it.
542
	 *
543
	 * Takes the task passed in the constructor as the task to be sent; recipient should
544
	 * be pre-existing. The task request will be sent to all recipients.
545
	 *
546
	 * @param string $prefix
547
	 *
548
	 * @return true
549
	 */
550
	public function sendTaskRequest($prefix): bool {
551
		// Generate a TaskGlobalObjectId
552
		$taskid = $this->createTGOID();
553
		$messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
554
555
		// Set properties on Task Request
556
		mapi_setprops($this->message, [
557
			$this->props['task_goid'] => $taskid, // our new task_goid
558
			$this->props['taskstate'] => tdsACC, // state for our outgoing request
559
			$this->props['taskmode'] => tdmtNothing, // we're not sending a change
560
			$this->props['updatecount'] => 2, // version 2 (no idea)
561
			$this->props['task_acceptance_state'] => olTaskDelegationUnknown, // no reply yet
562
			$this->props['ownership'] => olDelegatedTask, // Task has been assigned
563
			$this->props['taskhistory'] => thAssigned, // Task has been assigned
564
			PR_CONVERSATION_TOPIC => $messageprops[PR_SUBJECT],
565
			PR_ICON_INDEX => ICON_TASK_ASSIGNER,
566
		]);
567
		$this->setLastUser();
568
		$this->setOwnerForAssignor();
569
		mapi_savechanges($this->message);
570
571
		// Create outgoing task request message
572
		$outgoing = $this->createOutgoingMessage();
573
574
		// No need to copy PR_ICON_INDEX and  PR_SENT_* information in to outgoing message.
575
		$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];
576
		mapi_copyto($this->message, [], $ignoreProps, $outgoing);
577
578
		// Make it a task request, and put it in sent items after it is sent
579
		mapi_setprops($outgoing, [
580
			PR_MESSAGE_CLASS => "IPM.TaskRequest", // class is task request
581
			$this->props['taskstate'] => tdsOWN, // for the recipient he is the task owner
582
			$this->props['taskmode'] => tdmtTaskReq, // for the recipient it's a request
583
			$this->props['updatecount'] => 1, // version 2 is in the attachment
584
			PR_SUBJECT_PREFIX => $prefix,
585
			PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
586
		]);
587
588
		$attach = mapi_message_createattach($outgoing);
589
		mapi_setprops($attach, [
590
			PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG,
591
			PR_ATTACHMENT_HIDDEN => true,
592
			PR_DISPLAY_NAME => $messageprops[PR_SUBJECT], ]);
593
594
		$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
0 ignored issues
show
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

594
		$sub = /** @scrutinizer ignore-call */ mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
Loading history...
595
596
		mapi_copyto($this->message, [], [], $sub);
597
		mapi_setprops($sub, [PR_MESSAGE_CLASS => 'IPM.Task']);
598
599
		mapi_savechanges($sub);
600
601
		mapi_savechanges($attach);
602
603
		mapi_savechanges($outgoing);
604
		mapi_message_submitmessage($outgoing);
605
606
		return true;
607
	}
608
609
	// Assignee functions (called by the assignee)
610
611
	/**
612
	 * Updates task version counter.
613
	 *
614
	 * Must be called before each update to increase counter.
615
	 */
616
	public function updateTaskRequest(): void {
617
		$messageprops = mapi_getprops($this->message, [$this->props['updatecount']]);
618
619
		if (isset($messageprops)) {
620
			++$messageprops[$this->props['updatecount']];
621
		}
622
		else {
623
			$messageprops[$this->props['updatecount']] = 1;
624
		}
625
626
		mapi_setprops($this->message, $messageprops);
627
	}
628
629
	/**
630
	 * Processes a task request.
631
	 *
632
	 * Message passed should be an IPM.TaskRequest message. The task request is then processed to create
633
	 * the task in the tasks folder if needed.
634
	 *
635
	 * @return bool
636
	 */
637
	public function processTaskRequest() {
638
		if (!$this->isTaskRequest()) {
639
			return false;
640
		}
641
		$messageProps = mapi_getprops($this->message, [PR_PROCESSED, $this->props["taskupdates"], PR_MESSAGE_TO_ME]);
642
		if (isset($messageProps[PR_PROCESSED]) && $messageProps[PR_PROCESSED]) {
643
			return true;
644
		}
645
646
		// if task is updated in task folder then we don't need to process
647
		// old request.
648
		if ($this->isTaskRequestUpdated()) {
649
			return true;
650
		}
651
652
		$isReceivedItem = $this->isReceivedItem($messageProps);
653
654
		$props = [];
655
		$props[PR_PROCESSED] = true;
656
		$props[$this->props["taskstate"]] = $isReceivedItem ? tdsOWN : tdsACC;
657
		$props[$this->props["ownership"]] = $isReceivedItem ? olOwnTask : olDelegatedTask;
658
659
		mapi_setprops($this->message, $props);
660
		mapi_savechanges($this->message);
661
662
		// Don't create associated task in task folder if "taskupdates" is not true.
663
		if (!$isReceivedItem && !$messageProps[$this->props["taskupdates"]]) {
664
			return true;
665
		}
666
		// create an associated task in task folder while
667
		// reading/loading task request on client side.
668
		$task = $this->getAssociatedTask(true);
669
670
		$taskProps = mapi_getprops($task, [$this->props['taskmultrecips']]);
671
		$taskProps[$this->props["taskstate"]] = $isReceivedItem ? tdsOWN : tdsACC;
672
		$taskProps[$this->props["taskhistory"]] = thAssigned;
673
		$taskProps[$this->props["taskmode"]] = tdmtNothing;
674
		$taskProps[$this->props["taskaccepted"]] = false;
675
		$taskProps[$this->props["taskfcreator"]] = false;
676
		$taskProps[$this->props["ownership"]] = $isReceivedItem ? olOwnTask : olDelegatedTask;
677
		$taskProps[$this->props["task_acceptance_state"]] = olTaskNotDelegated;
678
		$taskProps[PR_ICON_INDEX] = ICON_TASK_ASSIGNEE;
679
680
		mapi_setprops($task, $taskProps);
681
		$this->setAssignorInRecipients($task);
682
683
		mapi_savechanges($task);
684
685
		return true;
686
	}
687
688
	/**
689
	 * Accepts a task request and sends the response.
690
	 *
691
	 * Message passed should be an IPM.Task (eg the task from getAssociatedTask())
692
	 *
693
	 * Copies the task to the user's task folder, sets it to accepted, and sends the acceptation
694
	 * message back to the organizer. The caller is responsible for removing the message.
695
	 *
696
	 * @return array|false PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the task
697
	 */
698
	public function doAccept() {
699
		$prefix = _("Task Accepted:") . " ";
700
		$messageProps = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['taskstate']]);
701
702
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
703
			// Can only accept assignee task
704
			return false;
705
		}
706
707
		$this->setLastUser();
708
		$this->updateTaskRequest();
709
710
		$props = [
711
			$this->props['taskhistory'] => thAccepted,
712
			$this->props['task_assigned_time'] => time(),
713
			$this->props['taskaccepted'] => true,
714
			$this->props['task_acceptance_state'] => olTaskNotDelegated, ];
715
716
		// Message is TaskRequest then update the associated task as well.
717
		if ($this->isTaskRequest($messageProps[PR_MESSAGE_CLASS])) {
718
			$task = $this->getAssociatedTask(false);
719
			if ($task) {
720
				mapi_setprops($task, $props);
721
				mapi_savechanges($task);
722
			}
723
		}
724
725
		// Set as accepted
726
		mapi_setprops($this->message, $props);
727
728
		// As we copy the all properties from received message we need to remove following
729
		// properties from accept response.
730
		mapi_deleteprops($this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
731
732
		mapi_savechanges($this->message);
733
734
		$this->sendResponse(tdmtTaskAcc, $prefix);
735
736
		return $this->deleteReceivedTR();
737
	}
738
739
	/**
740
	 * Declines a task request and sends the response.
741
	 *
742
	 * Passed message must be a task request message, ie isTaskRequest() must return TRUE.
743
	 *
744
	 * Sends the decline message back to the organizer. The caller is responsible for removing the message.
745
	 *
746
	 * @return array|false TRUE on success, FALSE on failure
747
	 */
748
	public function doDecline() {
749
		$prefix = _("Task Declined:") . " ";
750
		$messageProps = mapi_getprops($this->message, [$this->props['taskstate']]);
751
752
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
753
			return false; // Can only decline assignee task
754
		}
755
756
		$this->setLastUser();
757
		$this->updateTaskRequest();
758
759
		// Set as declined
760
		mapi_setprops($this->message, [
761
			$this->props['taskhistory'] => thDeclined,
762
			$this->props['task_acceptance_state'] => olTaskDelegationDeclined,
763
		]);
764
		mapi_deleteprops($this->message, [PR_MESSAGE_RECIP_ME, PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME, PR_PROCESSED]);
765
		mapi_savechanges($this->message);
766
767
		$this->sendResponse(tdmtTaskDec, $prefix);
768
769
		// Delete the associated task when task request is declined by the assignee.
770
		$task = $this->getAssociatedTask(false);
771
		if ($task) {
772
			$taskFolder = $this->getDefaultTasksFolder();
773
			$props = mapi_getprops($task, [PR_ENTRYID]);
774
			mapi_folder_deletemessages($taskFolder, [$props[PR_ENTRYID]]);
775
		}
776
777
		return $this->deleteReceivedTR();
778
	}
779
780
	/**
781
	 * Sends an update of the task if requested, and sends the Status-On-Completion report if complete and requested.
782
	 *
783
	 * If no updates were requested from the organizer, this function does nothing.
784
	 *
785
	 * @return bool TRUE if the update succeeded, FALSE otherwise
786
	 */
787
	public function doUpdate() {
788
		$messageProps = mapi_getprops($this->message, [$this->props['taskstate'], PR_SUBJECT]);
789
790
		if (!isset($messageProps[$this->props['taskstate']]) || $messageProps[$this->props['taskstate']] != tdsOWN) {
791
			return false; // Can only update assignee task
792
		}
793
794
		$this->setLastUser();
795
		$this->updateTaskRequest();
796
797
		// Set as updated
798
		mapi_setprops($this->message, [$this->props['taskhistory'] => thUpdated]);
799
800
		mapi_savechanges($this->message);
801
802
		$props = mapi_getprops($this->message, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['recurring'], $this->props['complete']]);
803
		if (!$props[$this->props['complete']] && $props[$this->props['taskupdates']] && !(isset($props[$this->props['recurring']]) && $props[$this->props['recurring']])) {
804
			$this->sendResponse(tdmtTaskUpd, _("Task Updated:") . " ");
805
		}
806
		elseif ($props[$this->props['complete']]) {
807
			$this->sendResponse(tdmtTaskUpd, _("Task Completed:") . " ");
808
		}
809
810
		return true;
811
	}
812
813
	/**
814
	 * Gets the store associated with the task.
815
	 *
816
	 * Normally this will just open the store that the processed message is in. However, if the message is opened
817
	 * by a delegate, this function opens the store that the message was delegated from.
818
	 */
819
	public function getTaskFolderStore() {
820
		$ownerentryid = false;
821
822
		$rcvdprops = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
823
		if (isset($rcvdprops[PR_RCVD_REPRESENTING_ENTRYID])) {
824
			$ownerentryid = $rcvdprops[PR_RCVD_REPRESENTING_ENTRYID];
825
		}
826
827
		if (!$ownerentryid) {
828
			$store = $this->store;
829
		}
830
		else {
831
			$ab = mapi_openaddressbook($this->session);
832
			if (!$ab) {
833
				return false;
834
			}
835
836
			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
837
			if (!$mailuser) {
838
				return false;
839
			}
840
841
			$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
842
			if (!isset($mailuserprops[PR_EMAIL_ADDRESS])) {
843
				return false;
844
			}
845
846
			$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
847
848
			$store = mapi_openmsgstore($this->session, $storeid);
849
		}
850
851
		return $store;
852
	}
853
854
	/**
855
	 * Opens the default task folder for the current user, or the specified user if passed.
856
	 */
857
	public function getDefaultTasksFolder() {
858
		$store = $this->getTaskFolderStore();
859
860
		$inbox = mapi_msgstore_getreceivefolder($store);
861
		$inboxprops = mapi_getprops($inbox, [PR_IPM_TASK_ENTRYID]);
862
		if (!isset($inboxprops[PR_IPM_TASK_ENTRYID])) {
863
			return false;
864
		}
865
866
		return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_TASK_ENTRYID]);
867
	}
868
869
	/**
870
	 * Prepares the sent representing properties from given MAPI store.
871
	 *
872
	 * @param mixed $store MAPI store object
873
	 *
874
	 * @return array[][][][][]|false if store is not mail box owner entryid then return false else prepare the sent representing props and return it
875
	 *
876
	 * @psalm-return array<array<array<array<array<array<never, never>>>>>>|false
877
	 */
878
	public function getSentReprProps($store) {
879
		$storeprops = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
880
		if (!isset($storeprops[PR_MAILBOX_OWNER_ENTRYID])) {
881
			return false;
882
		}
883
884
		$ab = mapi_openaddressbook($this->session);
885
		$mailuser = mapi_ab_openentry($ab, $storeprops[PR_MAILBOX_OWNER_ENTRYID]);
886
		$mailuserprops = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_ENTRYID]);
887
888
		$props = [];
889
		$props[PR_SENT_REPRESENTING_ADDRTYPE] = $mailuserprops[PR_ADDRTYPE];
890
		$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $mailuserprops[PR_EMAIL_ADDRESS];
891
		$props[PR_SENT_REPRESENTING_NAME] = $mailuserprops[PR_DISPLAY_NAME];
892
		$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $mailuserprops[PR_SEARCH_KEY];
893
		$props[PR_SENT_REPRESENTING_ENTRYID] = $mailuserprops[PR_ENTRYID];
894
895
		return $props;
896
	}
897
898
	/**
899
	 * Creates an outgoing message based on the passed message - will set delegate information
900
	 * and sent mail folder.
901
	 */
902
	public function createOutgoingMessage() {
903
		// Open our default store for this user (that's the only store we can submit in)
904
		$store = $this->getDefaultStore();
905
		$storeprops = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID]);
906
907
		$outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
908
		if (!$outbox) {
909
			return false;
910
		}
911
912
		$outgoing = mapi_folder_createmessage($outbox);
913
		if (!$outgoing) {
914
			return false;
915
		}
916
917
		// Set SENT_REPRESENTING in case we're sending as a delegate
918
		$ownerstore = $this->getTaskFolderStore();
919
		$sentreprprops = $this->getSentReprProps($ownerstore);
920
		mapi_setprops($outgoing, $sentreprprops);
921
922
		mapi_setprops($outgoing, [PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID]]);
923
924
		return $outgoing;
925
	}
926
927
	/**
928
	 * Sends a response message (from assignee back to organizer).
929
	 *
930
	 * @param int   $type   Type of response (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd)
931
	 * @param mixed $prefix
932
	 *
933
	 * @return bool TRUE on success
934
	 */
935
	public function sendResponse($type, $prefix) {
936
		// Create a message in our outbox
937
		$outgoing = $this->createOutgoingMessage();
938
		$messageprops = mapi_getprops($this->message, [PR_CONVERSATION_TOPIC, PR_MESSAGE_CLASS, $this->props['complete']]);
939
940
		$attach = mapi_message_createattach($outgoing);
941
		mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_CONVERSATION_TOPIC], PR_ATTACHMENT_HIDDEN => true]);
942
		$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
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

942
		$sub = /** @scrutinizer ignore-call */ mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
Loading history...
943
944
		$message = !$this->isTaskRequest() ? $this->message : $this->getAssociatedTask(false);
945
946
		$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];
947
948
		mapi_copyto($message, [], $ignoreProps, $outgoing);
949
		mapi_copyto($message, [], [], $sub);
950
951
		if (!$this->setRecipientsForResponse($outgoing, $type)) {
952
			return false;
953
		}
954
955
		$props = [];
956
957
		switch ($type) {
958
			case tdmtTaskAcc:
959
				$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Accept";
960
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_ASSIGNER]);
961
				break;
962
963
			case tdmtTaskDec:
964
				$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Decline";
965
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_DECLINE]);
966
				break;
967
968
			case tdmtTaskUpd:
969
				mapi_setprops($sub, [PR_ICON_INDEX => ICON_TASK_ASSIGNER]);
970
				if ($messageprops[$this->props['complete']]) {
971
					$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Complete";
972
				}
973
				else {
974
					$props[PR_MESSAGE_CLASS] = "IPM.TaskRequest.Update";
975
				}
976
977
				break;
978
		}
979
980
		mapi_savechanges($sub);
981
		mapi_savechanges($attach);
982
983
		$props[PR_SUBJECT] = $prefix . $messageprops[PR_CONVERSATION_TOPIC];
984
		$props[$this->props['taskmode']] = $type;
985
		$props[$this->props['task_assigned_time']] = time();
986
987
		mapi_setprops($outgoing, $props);
988
989
		// taskCommentsInfo contains some comments which added by assignee while
990
		// edit response before sending task response.
991
		if ($this->taskCommentsInfo != '') {
992
			$comments = $this->getTaskCommentsInfo();
993
			$stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, STGM_TRANSACTED, MAPI_CREATE | MAPI_MODIFY);
994
			mapi_stream_setsize($stream, strlen($comments));
995
			mapi_stream_write($stream, $comments);
996
			mapi_stream_commit($stream);
997
		}
998
999
		mapi_savechanges($outgoing);
1000
		mapi_message_submitmessage($outgoing);
1001
1002
		return true;
1003
	}
1004
1005
	public function getDefaultStore() {
1006
		$table = mapi_getmsgstorestable($this->session);
1007
		$rows = mapi_table_queryallrows($table, [PR_DEFAULT_STORE, PR_ENTRYID]);
1008
1009
		foreach ($rows as $row) {
1010
			if ($row[PR_DEFAULT_STORE]) {
1011
				return mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
1012
			}
1013
		}
1014
1015
		return false;
1016
	}
1017
1018
	/**
1019
	 * Creates a new TaskGlobalObjId.
1020
	 *
1021
	 * Just 16 bytes of random data
1022
	 */
1023
	public function createTGOID(): string {
1024
		$goid = "";
1025
		for ($i = 0; $i < 16; ++$i) {
1026
			$goid .= chr(rand(0, 255));
1027
		}
1028
1029
		return $goid;
1030
	}
1031
1032
	/**
1033
	 * Gets the embedded task of task request. Further used to
1034
	 * create/update associated task of assigner/assignee.
1035
	 *
1036
	 * @return bool|resource embedded task if found else false
1037
	 */
1038
	public function getEmbeddedTask() {
1039
		$task = false;
1040
		$goid = mapi_getprops($this->message, [$this->props["task_goid"]]);
1041
		$attachmentTable = mapi_message_getattachmenttable($this->message);
1042
		$restriction = [RES_PROPERTY,
1043
			[RELOP => RELOP_EQ,
1044
				ULPROPTAG => PR_ATTACH_METHOD,
1045
				VALUE => ATTACH_EMBEDDED_MSG, ],
1046
		];
1047
		$rows = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM], $restriction);
1048
1049
		if (empty($rows)) {
1050
			return $task;
1051
		}
1052
1053
		foreach ($rows as $row) {
1054
			try {
1055
				$attach = mapi_message_openattach($this->message, $row[PR_ATTACH_NUM]);
1056
				$task = mapi_attach_openobj($attach);
1057
			}
1058
			catch (MAPIException $e) {
1059
				continue;
1060
			}
1061
1062
			$taskGoid = mapi_getprops($task, [$this->props["task_goid"]]);
1063
			if ($goid[$this->props["task_goid"]] === $taskGoid[$this->props["task_goid"]]) {
1064
				mapi_setprops($attach, [PR_ATTACHMENT_HIDDEN => true]);
1065
				mapi_savechanges($attach);
1066
				mapi_savechanges($this->message);
1067
				break;
1068
			}
1069
		}
1070
1071
		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...
1072
	}
1073
1074
	/**
1075
	 * Sets the user name who has last used this task. Update the
1076
	 * tasklastdelegate and task_assigned_time.
1077
	 */
1078
	public function setLastUser(): void {
1079
		$delegatestore = $this->getDefaultStore();
1080
		$taskstore = $this->getTaskFolderStore();
1081
1082
		$delegateprops = mapi_getprops($delegatestore, [PR_MAILBOX_OWNER_NAME]);
1083
		$taskprops = mapi_getprops($taskstore, [PR_MAILBOX_OWNER_NAME]);
1084
1085
		// The owner of the task
1086
		$username = $delegateprops[PR_MAILBOX_OWNER_NAME];
1087
		// This is me (the one calling the script)
1088
		$delegate = $taskprops[PR_MAILBOX_OWNER_NAME];
1089
1090
		if ($this->isTaskRequest()) {
1091
			$task = $this->getAssociatedTask(false);
1092
			mapi_setprops($task, [
1093
				$this->props["tasklastuser"] => $username,
1094
				$this->props["tasklastdelegate"] => $delegate,
1095
				$this->props['task_assigned_time'] => time(),
1096
			]);
1097
			mapi_savechanges($task);
1098
		}
1099
		mapi_setprops($this->message, [
1100
			$this->props["tasklastuser"] => $username,
1101
			$this->props["tasklastdelegate"] => $delegate,
1102
			$this->props['task_assigned_time'] => time(),
1103
		]);
1104
	}
1105
1106
	/**
1107
	 * Sets assignee as owner in the assignor's copy of task.
1108
	 * Assignee becomes the owner when a user/assignor assigns any task to someone.
1109
	 * There can be more than one assignee.
1110
	 */
1111
	public function setOwnerForAssignor(): void {
1112
		$recipTable = mapi_message_getrecipienttable($this->message);
1113
		$recips = mapi_table_queryallrows($recipTable, [PR_DISPLAY_NAME]);
1114
1115
		if (!empty($recips)) {
1116
			$owner = [];
1117
			foreach ($recips as $value) {
1118
				$owner[] = $value[PR_DISPLAY_NAME];
1119
			}
1120
1121
			$props = [$this->props['owner'] => implode("; ", $owner)];
1122
			mapi_setprops($this->message, $props);
1123
		}
1124
	}
1125
1126
	/**
1127
	 * Sets assignor as recipients in assignee's copy of task.
1128
	 *
1129
	 * If assignor has requested task updates then the assignor is added as recipient type MAPI_CC.
1130
	 *
1131
	 * Also if assignor has request SOC then the assignor is also add as recipient type MAPI_BCC
1132
	 *
1133
	 * @param mixed $task assignee's copy of task
1134
	 */
1135
	public function setAssignorInRecipients($task): void {
1136
		$recipTable = mapi_message_getrecipienttable($task);
1137
1138
		// Delete all MAPI_TO recipients
1139
		$recips = mapi_table_queryallrows($recipTable, [PR_ROWID], [
1140
			RES_PROPERTY,
1141
			[
1142
				RELOP => RELOP_EQ,
1143
				ULPROPTAG => PR_RECIPIENT_TYPE,
1144
				VALUE => MAPI_TO,
1145
			],
1146
		]);
1147
		foreach ($recips as $recip) {
1148
			mapi_message_modifyrecipients($task, MODRECIP_REMOVE, [$recip]);
1149
		}
1150
1151
		$recips = [];
1152
		$taskReqProps = mapi_getprops($this->message, [
1153
			PR_SENT_REPRESENTING_NAME,
1154
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1155
			PR_SENT_REPRESENTING_ENTRYID,
1156
			PR_SENT_REPRESENTING_ADDRTYPE,
1157
			PR_SENT_REPRESENTING_SEARCH_KEY,
1158
		]);
1159
		$associatedTaskProps = mapi_getprops($task, [
1160
			$this->props['taskupdates'],
1161
			$this->props['tasksoc'],
1162
			$this->props['taskmultrecips'],
1163
		]);
1164
1165
		// Build assignor info
1166
		$assignor = [
1167
			PR_ENTRYID => $taskReqProps[PR_SENT_REPRESENTING_ENTRYID],
1168
			PR_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
1169
			PR_EMAIL_ADDRESS => $taskReqProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
1170
			PR_RECIPIENT_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
1171
			PR_ADDRTYPE => empty($taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE],
1172
			PR_RECIPIENT_FLAGS => recipSendable,
1173
			PR_SEARCH_KEY => $taskReqProps[PR_SENT_REPRESENTING_SEARCH_KEY],
1174
		];
1175
1176
		// Assignor has requested task updates, so set him/her as MAPI_CC in recipienttable.
1177
		if ((isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['taskupdates']]) &&
1178
			!(isset($associatedTaskProps[$this->props['taskmultrecips']]) && $associatedTaskProps[$this->props['taskmultrecips']] == tmrReceived)) {
1179
			$assignor[PR_RECIPIENT_TYPE] = MAPI_CC;
1180
			$recips[] = $assignor;
1181
		}
1182
1183
		// Assignor wants to receive an email report when task is mark as 'Complete', so in recipients as MAPI_BCC
1184
		if ($associatedTaskProps[$this->props['tasksoc']]) {
1185
			$assignor[PR_RECIPIENT_TYPE] = MAPI_BCC;
1186
			$recips[] = $assignor;
1187
		}
1188
1189
		if (!empty($recips)) {
1190
			mapi_message_modifyrecipients($task, MODRECIP_ADD, $recips);
1191
		}
1192
	}
1193
1194
	/**
1195
	 * Deletes incoming task request from Inbox.
1196
	 *
1197
	 * @returns array|bool PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the deleted task request
1198
	 *
1199
	 * @return array|false
1200
	 */
1201
	public function deleteReceivedTR() {
1202
		$store = $this->getTaskFolderStore();
1203
		$storeType = mapi_getprops($store, [PR_MDB_PROVIDER]);
1204
		if ($storeType[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1205
			$store = $this->getDefaultStore();
1206
		}
1207
		$inbox = mapi_msgstore_getreceivefolder($store);
1208
1209
		$storeProps = mapi_getprops($store, [PR_IPM_WASTEBASKET_ENTRYID]);
1210
		$props = mapi_getprops($this->message, [$this->props['task_goid']]);
1211
		$goid = $props[$this->props['task_goid']];
1212
1213
		// Find the task by looking for the task_goid
1214
		$restriction = [
1215
			RES_PROPERTY,
1216
			[
1217
				RELOP => RELOP_EQ,
1218
				ULPROPTAG => $this->props['task_goid'],
1219
				VALUE => $goid,
1220
			],
1221
		];
1222
1223
		$contents = mapi_folder_getcontentstable($inbox);
1224
1225
		$rows = mapi_table_queryallrows($contents, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID], $restriction);
1226
1227
		if (!empty($rows)) {
1228
			// If there are multiple, just use the first
1229
			$entryid = $rows[0][PR_ENTRYID];
1230
			$wastebasket = mapi_msgstore_openentry($store, $storeProps[PR_IPM_WASTEBASKET_ENTRYID]);
1231
			mapi_folder_copymessages($inbox, [$entryid], $wastebasket, MESSAGE_MOVE);
1232
1233
			return [PR_ENTRYID => $entryid, PR_PARENT_ENTRYID => $rows[0][PR_PARENT_ENTRYID], PR_STORE_ENTRYID => $rows[0][PR_STORE_ENTRYID]];
1234
		}
1235
1236
		return false;
1237
	}
1238
1239
	/**
1240
	 * Sets recipients for the outgoing message according to type of the response.
1241
	 *
1242
	 * If it is a task update, then only recipient type MAPI_CC are taken from the task message.
1243
	 *
1244
	 * If it is accept/decline response, then PR_SENT_REPRESENTATING_XXXX are taken as recipient.
1245
	 *
1246
	 * @param mixed $outgoing     outgoing mapi message
1247
	 * @param int   $responseType response type (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd)
1248
	 */
1249
	public function setRecipientsForResponse($outgoing, $responseType): bool {
1250
		// Clear recipients from outgoing msg
1251
		$this->deleteAllRecipients($outgoing);
1252
1253
		// If it is a task update then get MAPI_CC recipients which are assignors who has asked for task update.
1254
		if ($responseType == tdmtTaskUpd) {
1255
			$props = mapi_getprops($this->message, [$this->props['complete']]);
1256
			$isComplete = $props[$this->props['complete']];
1257
1258
			$recipTable = mapi_message_getrecipienttable($this->message);
1259
			$recips = mapi_table_queryallrows($recipTable, $this->recipProps, [
1260
				RES_PROPERTY,
1261
				[
1262
					RELOP => RELOP_EQ,
1263
					ULPROPTAG => PR_RECIPIENT_TYPE,
1264
					VALUE => ($isComplete ? MAPI_BCC : MAPI_CC),
1265
				],
1266
			]);
1267
1268
			// No recipients found, return error
1269
			if (empty($recips)) {
1270
				return false;
1271
			}
1272
1273
			foreach ($recips as $recip) {
1274
				$recip[PR_RECIPIENT_TYPE] = MAPI_TO;	// Change recipient type to MAPI_TO
1275
				mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
1276
			}
1277
1278
			return true;
1279
		}
1280
1281
		$orgprops = mapi_getprops($this->message, [
1282
			PR_SENT_REPRESENTING_NAME,
1283
			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1284
			PR_SENT_REPRESENTING_ADDRTYPE,
1285
			PR_SENT_REPRESENTING_ENTRYID,
1286
			PR_SUBJECT,
1287
		]);
1288
1289
		$recip = [
1290
			PR_DISPLAY_NAME => $orgprops[PR_SENT_REPRESENTING_NAME],
1291
			PR_EMAIL_ADDRESS => $orgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
1292
			PR_ADDRTYPE => $orgprops[PR_SENT_REPRESENTING_ADDRTYPE],
1293
			PR_ENTRYID => $orgprops[PR_SENT_REPRESENTING_ENTRYID],
1294
			PR_RECIPIENT_TYPE => MAPI_TO, ];
1295
1296
		mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
1297
1298
		return true;
1299
	}
1300
1301
	/**
1302
	 * Deletes all recipients from given message object.
1303
	 *
1304
	 * @param mixed $message MAPI message from which recipients are to be removed
1305
	 */
1306
	public function deleteAllRecipients($message): void {
1307
		$recipTable = mapi_message_getrecipienttable($message);
1308
		$recipRows = mapi_table_queryallrows($recipTable, [PR_ROWID]);
1309
1310
		foreach ($recipRows as $recipient) {
1311
			mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1312
		}
1313
	}
1314
1315
	/**
1316
	 * Marks the record to complete and send complete update
1317
	 * notification to assigner.
1318
	 *
1319
	 * @return bool TRUE if the update succeeded, FALSE otherwise
1320
	 */
1321
	public function sendCompleteUpdate() {
1322
		$messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
1323
1324
		if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
1325
			return false; // Can only decline assignee task
1326
		}
1327
1328
		mapi_setprops($this->message, [
1329
			$this->props['complete'] => true,
1330
			$this->props['datecompleted'] => time(),
1331
			$this->props['status'] => 2,
1332
			$this->props['percent_complete'] => 1,
1333
		]);
1334
1335
		return $this->doUpdate();
1336
	}
1337
1338
	/**
1339
	 * Returns extra info about task request comments along with message body
1340
	 * which will be included in body while sending task request/response.
1341
	 *
1342
	 * @return string info about task request comments along with message body
1343
	 */
1344
	public function getTaskCommentsInfo() {
1345
		return $this->taskCommentsInfo;
1346
	}
1347
1348
	/**
1349
	 * Sets an extra info about task request comments along with message body
1350
	 * which will be included in body while sending task request/response.
1351
	 *
1352
	 * @param string $taskCommentsInfo info about task request comments along with message body
1353
	 */
1354
	public function setTaskCommentsInfo($taskCommentsInfo): void {
1355
		$this->taskCommentsInfo = $taskCommentsInfo;
1356
	}
1357
1358
	public function getSubProperties() {
1359
		$subProperties = [];
1360
		$subProperties["subject"] = PR_SUBJECT;
1361
		$subProperties["convtopic"] = PR_CONVERSATION_TOPIC;
1362
		$subProperties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete;
1363
		$subProperties["datecompleted"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted;
1364
		$subProperties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
1365
		$subProperties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate;
1366
		$subProperties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate;
1367
		$subProperties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus;
1368
		$subProperties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete;
1369
		$subProperties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
1370
		$subProperties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
1371
		$subProperties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
1372
		$subProperties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
1373
		$subProperties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
1374
		$subProperties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
1375
1376
		return getPropIdsFromStrings($this->store, $subProperties);
1377
	}
1378
}
1379