ItemModule::handleException()   D
last analyzed

Complexity

Conditions 31
Paths 40

Size

Total Lines 124
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 31
eloc 84
c 0
b 0
f 0
nc 40
nop 6
dl 0
loc 124
rs 4.1666

How to fix   Long Method    Complexity   

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
 * ItemModule
5
 * Module which opens, creates, saves and deletes an item. It
6
 * extends the Module class.
7
 */
8
class ItemModule extends Module {
9
	/**
10
	 * The setting whether Meeting Requests should be booked directly or not.
11
	 */
12
	public $directBookingMeetingRequest;
13
14
	/**
15
	 * The array of properties which should not be copied during the copy() action.
16
	 */
17
	public $skipCopyProperties;
18
19
	/**
20
	 * Indicates that we are supporting only plain text body in the message props.
21
	 */
22
	public $plaintext;
23
24
	/**
25
	 * Constructor.
26
	 *
27
	 * @param int   $id   unique id
28
	 * @param array $data list of all actions
29
	 */
30
	public function __construct($id, $data) {
31
		$this->directBookingMeetingRequest = ENABLE_DIRECT_BOOKING;
32
		$this->skipCopyProperties = [];
33
		$this->plaintext = false;
34
35
		parent::__construct($id, $data);
36
	}
37
38
	/**
39
	 * Executes all the actions in the $data variable.
40
	 */
41
	#[Override]
42
	public function execute() {
43
		foreach ($this->data as $actionType => $action) {
44
			if (!isset($actionType)) {
45
				continue;
46
			}
47
48
			try {
49
				$store = $this->getActionStore($action);
50
				$parententryid = $this->getActionParentEntryID($action);
51
				$entryid = $this->getActionEntryID($action);
52
53
				switch ($actionType) {
54
					case "open":
55
						$this->open($store, $entryid, $action);
56
						break;
57
58
					case "save":
59
						if (!$store || !$parententryid) {
60
							/*
61
							 * if parententryid or storeentryid is not passed then we can take a guess that
62
							 * it would be  a save operation but instead of depending on server to get default
63
							 * parent and store client should always send parententryid and storeentryid
64
							 *
65
							 * we can also assume that user has permission to right in his own store
66
							 */
67
							$this->save($store, $parententryid, $entryid, $action);
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of ItemModule::save(). ( Ignorable by Annotation )

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

67
							$this->save($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
68
							break;
69
						}
70
						/*
71
						 * The "message_action" object has been set, check the action_type field for
72
						 * the exact action which must be taken.
73
						 * Supported actions:
74
						 *   - acceptmeetingrequest: attendee has accepted mr
75
						 *   - declineMeetingRequest: attendee has declined mr
76
						 */
77
						if (!isset($action["message_action"], $action["message_action"]["action_type"])) {
78
							$this->save($store, $parententryid, $entryid, $action);
79
							break;
80
						}
81
82
						switch ($action["message_action"]["action_type"]) {
83
							case "declineMeetingRequest":
84
							case "acceptMeetingRequest":
85
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
86
								$basedate = ($action['basedate'] ?? false);
87
								$delete = false;
88
89
								if ($basedate) {
90
									$recurrence = new Recurrence($store, $message);
0 ignored issues
show
Bug introduced by
The type Recurrence was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
91
									$exceptionatt = $recurrence->getExceptionAttachment($basedate);
92
									if ($exceptionatt) {
93
										// get properties of existing exception.
94
										$exceptionattProps = mapi_getprops($exceptionatt, [PR_ATTACH_NUM]);
95
										$attach_num = $exceptionattProps[PR_ATTACH_NUM];
96
									}
97
								}
98
99
								/**
100
								 * Get message class from original message. This can be changed to
101
								 * IPM.Appointment if the item is a Meeting Request in the maillist.
102
								 * After Accepting/Declining the message is moved and changed.
103
								 */
104
								$originalMessageProps = mapi_getprops($message, [PR_MESSAGE_CLASS]);
105
								$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $this->directBookingMeetingRequest);
0 ignored issues
show
Bug introduced by
The type Meetingrequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
106
107
								// Update extra body information
108
								if (isset($action["message_action"]['meetingTimeInfo']) && !empty($action["message_action"]['meetingTimeInfo'])) {
109
									$req->setMeetingTimeInfo($action["message_action"]['meetingTimeInfo'],
110
										$action["message_action"]['mti_html'] ?? false);
111
									unset($action["message_action"]['meetingTimeInfo']);
112
								}
113
114
								// sendResponse flag if it is set then send the mail response to the organzer.
115
								$sendResponse = true;
116
								if (isset($action["message_action"]["sendResponse"]) && $action["message_action"]["sendResponse"] == false) {
117
									$sendResponse = false;
118
								}
119
120
								// @FIXME: fix body
121
								$body = false;
122
								if (isset($action["props"]["isHTML"]) && $action["props"]["isHTML"] === true) {
123
									$body = $action["props"]["html_body"] ?? false;
124
								}
125
								else {
126
									$body = $action["props"]["body"] ?? false;
127
								}
128
129
								if ($action["message_action"]["action_type"] == "acceptMeetingRequest") {
130
									$tentative = $action["message_action"]["responseType"] === olResponseTentative;
131
									$newProposedStartTime = $action["message_action"]["proposed_starttime"] ?? false;
132
									$newProposedEndTime = $action["message_action"]["proposed_endtime"] ?? false;
133
134
									// We are accepting MR from preview-read-mail so set delete the actual mail flag.
135
									$delete = $req->isMeetingRequest($originalMessageProps[PR_MESSAGE_CLASS]);
136
137
									$req->doAccept($tentative, $sendResponse, $delete, $newProposedStartTime, $newProposedEndTime, $body, true, $store, $basedate);
138
								}
139
								else {
140
									$delete = $req->doDecline($sendResponse, $basedate, $body);
141
								}
142
143
								/**
144
								 * Now if the item is the Meeting Request that was sent to the attendee
145
								 * it is removed when the user has clicked on Accept/Decline. If the
146
								 * item is the appointment in the calendar it will not be moved. To only
147
								 * notify the bus when the item is a Meeting Request we are going to
148
								 * check the PR_MESSAGE_CLASS and see if it is "IPM.Meeting*".
149
								 */
150
								$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
151
152
								// if opened appointment is exception then it will add
153
								// the attach_num and basedate in messageProps.
154
								if (isset($attach_num)) {
155
									$messageProps[PR_ATTACH_NUM] = [$attach_num];
156
									$messageProps[$this->properties["basedate"]] = $basedate;
157
								}
158
159
								if ($delete) {
160
									// send TABLE_DELETE event because the message has moved
161
									$this->sendFeedback(true);
162
									$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
163
								}
164
								else {
165
									$this->addActionData("update", ["item" => Conversion::mapMAPI2XML($this->properties, $messageProps)]);
166
									$GLOBALS["bus"]->addData($this->getResponseData());
167
168
									// send TABLE_SAVE event because an occurrence is deleted
169
									$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
170
								}
171
172
								break;
173
174
							case "acceptTaskRequest":
175
							case "declineTaskRequest":
176
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
177
178
								if (isset($action["props"]) && !empty($action["props"])) {
179
									$properties = $GLOBALS["properties"]->getTaskProperties();
180
									mapi_setprops($message, Conversion::mapXML2MAPI($properties, $action["props"]));
181
									mapi_savechanges($message);
182
								}
183
								// The task may be a delegated task, do an update if needed (will fail for non-delegated tasks)
184
								$tr = new TaskRequest($store, $message, $GLOBALS["mapisession"]->getSession());
0 ignored issues
show
Bug introduced by
The type TaskRequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
185
								$isAccept = $action["message_action"]["action_type"] == "acceptTaskRequest";
186
								if (isset($action["message_action"]["task_comments_info"]) && !empty($action["message_action"]["task_comments_info"])) {
187
									$tr->setTaskCommentsInfo($action["message_action"]["task_comments_info"]);
188
								}
189
								if ($isAccept) {
190
									$result = $tr->doAccept();
191
								}
192
								else {
193
									$result = $tr->doDecline();
194
								}
195
196
								$this->sendFeedback(true);
197
								if ($result !== false) {
198
									$GLOBALS["bus"]->notify(bin2hex((string) $result[PR_PARENT_ENTRYID]), TABLE_DELETE, $result);
199
								}
200
201
								$props = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
202
								if (!$tr->isTaskRequest()) {
203
									unset($props[PR_MESSAGE_CLASS]);
204
									$GLOBALS["bus"]->notify(bin2hex((string) $props[PR_PARENT_ENTRYID]), $isAccept ? TABLE_SAVE : TABLE_DELETE, $props);
205
								}
206
								break;
207
208
							case "copy":
209
							case "move":
210
								$this->copy($store, $parententryid, $entryid, $action);
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type resource expected by parameter $store of ItemModule::copy(). ( Ignorable by Annotation )

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

210
								$this->copy(/** @scrutinizer ignore-type */ $store, $parententryid, $entryid, $action);
Loading history...
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of ItemModule::copy(). ( Ignorable by Annotation )

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

210
								$this->copy($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
211
								break;
212
213
							case "reply":
214
							case "replyall":
215
							case "forward":
216
							default:
217
								$this->save($store, $parententryid, $entryid, $action);
218
						}
219
						break;
220
221
					case "delete":
222
						$subActionType = false;
223
						if (isset($action["message_action"], $action["message_action"]["action_type"])) {
224
							$subActionType = $action["message_action"]["action_type"];
225
						}
226
227
						/*
228
						 * The "message_action" object has been set, check the action_type field for
229
						 * the exact action which must be taken.
230
						 * Supported actions:
231
						 *   - cancelInvitation: organizer cancels already scheduled meeting
232
						 *   - removeFromCalendar: attendee receives meeting cancellation and wants to remove item from calendar
233
						 */
234
						switch ($subActionType) {
235
							case "removeFromCalendar":
236
								$basedate = (isset($action['basedate']) && !empty($action['basedate'])) ? $action['basedate'] : false;
237
238
								$this->removeFromCalendar($store, $entryid, $basedate, $this->directBookingMeetingRequest);
0 ignored issues
show
Bug introduced by
It seems like $basedate can also be of type false; however, parameter $basedate of ItemModule::removeFromCalendar() does only seem to accept string, 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

238
								$this->removeFromCalendar($store, $entryid, /** @scrutinizer ignore-type */ $basedate, $this->directBookingMeetingRequest);
Loading history...
239
								$this->sendFeedback(true);
240
								break;
241
242
							case "cancelInvitation":
243
								$this->cancelInvitation($store, $entryid, $action, $this->directBookingMeetingRequest);
244
								$this->sendFeedback(true);
245
								break;
246
247
							case "declineMeeting":
248
								// @FIXME can we somehow merge declineMeeting and declineMeetingRequest sub actions?
249
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
250
								$basedate = (isset($action['basedate']) && !empty($action['basedate'])) ? $action['basedate'] : false;
251
252
								$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $this->directBookingMeetingRequest);
253
254
								// @FIXME: may be we can remove this body check any get it while declining meeting 'body'
255
								$body = false;
256
								if (isset($action["props"]["isHTML"]) && $action["props"]["isHTML"] === true) {
257
									$body = $action["props"]["html_body"] ?? false;
258
								}
259
								else {
260
									$body = $action["props"]["body"] ?? false;
261
								}
262
								$req->doDecline(true, $basedate, $body);
263
264
								$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
265
								$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), $basedate ? TABLE_SAVE : TABLE_DELETE, $messageProps);
266
267
								break;
268
269
							case "snooze":
270
							case "dismiss":
271
								$this->delete($store, $parententryid, $entryid, $action);
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of ItemModule::delete(). ( Ignorable by Annotation )

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

271
								$this->delete($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
272
								break;
273
274
							default:
275
								// Deleting an occurrence means that we have to save the message to
276
								// generate an exception. So when the basedate is provided, we actually
277
								// perform a save rather then delete.
278
								if (isset($action['basedate']) && !empty($action['basedate'])) {
279
									$this->save($store, $parententryid, $entryid, $action, "delete");
280
								}
281
								else {
282
									$this->delete($store, $parententryid, $entryid, $action);
283
								}
284
								break;
285
						}
286
						break;
287
288
					default:
289
						$this->handleUnknownActionType($actionType);
290
				}
291
			}
292
			catch (MAPIException $e) {
0 ignored issues
show
Bug introduced by
The type MAPIException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
293
				$this->processException($e, $actionType, $store, $parententryid, $entryid, $action);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parententryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
294
			}
295
		}
296
	}
297
298
	/**
299
	 * Function does customization of exception based on module data.
300
	 * like, here it will generate display message based on actionType
301
	 * for particular exception.
302
	 *
303
	 * @param object     $e             Exception object
304
	 * @param string     $actionType    the action type, sent by the client
305
	 * @param MAPIobject $store         store object of message
0 ignored issues
show
Bug introduced by
The type MAPIobject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
306
	 * @param string     $parententryid parent entryid of the message
307
	 * @param string     $entryid       entryid of the message
308
	 * @param array      $action        the action data, sent by the client
309
	 */
310
	#[Override]
311
	public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
312
		if (is_null($e->displayMessage)) {
313
			switch ($actionType) {
314
				case "open":
315
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
316
						$e->setDisplayMessage(_("You have insufficient privileges to open this message."));
317
					}
318
					elseif ($e->getCode() == MAPI_E_NOT_FOUND) {
319
						$e->setDisplayMessage(_("Could not find message, either it has been moved or deleted or you don't have access to open this message."));
320
						// Show error in console instead of a pop-up
321
						if (isset($action["message_action"]['suppress_exception']) && $action["message_action"]['suppress_exception'] === true) {
322
							$e->setNotificationType('console');
323
						}
324
					}
325
					else {
326
						$e->setDisplayMessage(_("Could not open message."));
327
						$e->allowToShowDetailsMessage = true;
328
					}
329
					break;
330
331
				case "save":
332
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
333
						if (!empty($action["message_action"]["action_type"])) {
334
							switch ($action["message_action"]["action_type"]) {
335
								case "declineMeetingRequest":
336
									$e->setDisplayMessage(_("You have insufficient privileges to decline this Meeting Request") . ".");
337
									break;
338
339
								case "acceptMeetingRequest":
340
									$e->setDisplayMessage(_("You have insufficient privileges to accept this Meeting Request") . ".");
341
									break;
342
343
								case "copy":
344
									$e->setDisplayMessage(_("Could not copy message") . ".");
345
									break;
346
347
								case "move":
348
									$e->setDisplayMessage(_("Could not move message") . ".");
349
									break;
350
							}
351
						}
352
353
						if (empty($e->displayMessage)) {
354
							$e->setDisplayMessage(_("You have insufficient privileges to save items in this folder") . ".");
355
						}
356
					}
357
					elseif ($e->getCode() == MAPI_E_STORE_FULL) {
358
						$e->setDisplayMessage($this->getOverQuotaMessage($store));
359
					}
360
					else {
361
						$e->setDisplayMessage(_("Could not save message") . ".");
362
						$e->allowToShowDetailsMessage = true;
363
					}
364
					break;
365
366
				case 'delete':
367
					switch ($e->getCode()) {
368
						case MAPI_E_NO_ACCESS:
369
							if (!empty($action['message_action']['action_type'])) {
370
								switch ($action['message_action']['action_type']) {
371
									case 'removeFromCalendar':
372
										$e->setDisplayMessage(_('You have insufficient privileges to remove item from the calendar.'));
373
										break;
374
								}
375
							}
376
							break;
377
378
						case MAPI_E_NOT_IN_QUEUE:
379
							$e->setDisplayMessage(_('Message is no longer in the outgoing queue, typically because it has already been sent.'));
380
							break;
381
382
						case MAPI_E_UNABLE_TO_ABORT:
383
							$e->setDisplayMessage(_('Message cannot be aborted'));
384
							break;
385
					}
386
					if (empty($e->displayMessage)) {
387
						$e->setDisplayMessage(_("You have insufficient privileges to delete items in this folder") . ".");
388
					}
389
					break;
390
391
				case "attach_items":
392
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
393
						$e->setDisplayMessage(_("You have insufficient privileges to attach item as an attachment."));
394
					}
395
					else {
396
						$e->setDisplayMessage(_("Could not attach item as an attachment."));
397
					}
398
					break;
399
400
				case "reclaimownership":
401
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
402
						$e->setDisplayMessage(_("You have insufficient privileges to reclaim the ownership for the Task Request."));
403
					}
404
					else {
405
						$e->setDisplayMessage(_("Could not reclaim the ownership for the Task Request."));
406
					}
407
					break;
408
409
				case "acceptTaskRequest":
410
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
411
						$e->setDisplayMessage(_("You have insufficient privileges to accept this Task Request."));
412
					}
413
					else {
414
						$e->setDisplayMessage(_("Could not accept Task Request."));
415
					}
416
					break;
417
418
				case "declineTaskRequest":
419
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
420
						$e->setDisplayMessage(_("You have insufficient privileges to decline this Task Request."));
421
					}
422
					else {
423
						$e->setDisplayMessage(_("Could not decline Task Request."));
424
					}
425
					break;
426
			}
427
			Log::Write(
428
				LOGLEVEL_ERROR,
429
				"itemmodule::handleException():" . $actionType . ": " . $e->displayMessage
430
			);
431
		}
432
433
		parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
434
	}
435
436
	/**
437
	 * Function which opens an item.
438
	 *
439
	 * @param object $store   MAPI Message Store Object
440
	 * @param string $entryid entryid of the message
441
	 * @param array  $action  the action data, sent by the client
442
	 */
443
	public function open($store, $entryid, $action) {
444
		$data = [];
445
446
		if ($entryid) {
447
			if ($store) {
0 ignored issues
show
introduced by
$store is of type object, thus it always evaluated to true.
Loading history...
448
				$message = $GLOBALS['operations']->openMessage($store, $entryid);
449
			}
450
			else {
451
				// store is not passed so we need to open the message first to get the store resource
452
				$message = $GLOBALS['mapisession']->openMessage($entryid);
453
454
				$messageStoreInfo = mapi_getprops($message, [PR_STORE_ENTRYID]);
455
				$store = $GLOBALS['mapisession']->openMessageStore($messageStoreInfo[PR_STORE_ENTRYID]);
456
			}
457
		}
458
459
		if (empty($message)) {
460
			return;
461
		}
462
463
		// Detect whether S/MIME decoding or meeting request processing is required
464
		$props = mapi_getprops($message, [PR_MESSAGE_CLASS]);
465
		$messageClass = $props[PR_MESSAGE_CLASS] ?? '';
466
		$requiresSmime = stripos((string) $messageClass, 'SMIME') !== false; /* infix match(!), cannot use class_match_prefix */
467
		$requiresMeeting = class_match_prefix($messageClass, "IPM.Schedule.Meeting");
468
469
		// Decode S/MIME signed messages only when needed
470
		if ($requiresSmime) {
471
			parse_smime($store, $message);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.
Loading history...
472
		}
473
474
		// Open embedded message if requested
475
		$attachNum = !empty($action['attach_num']) ? $action['attach_num'] : false;
476
477
		if ($attachNum) {
478
			// get message props of sub message
479
			$parentMessage = $message;
480
			$message = $GLOBALS['operations']->openMessage($store, $entryid, $attachNum, true);
481
482
			if (empty($message)) {
483
				return;
484
			}
485
486
			$data['item'] = $GLOBALS['operations']->getEmbeddedMessageProps($store, $message, $this->properties, $parentMessage, $attachNum);
487
		}
488
		else {
489
			// get message props of the message
490
			$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $this->properties, $this->plaintext, true);
491
			$messageClass = $data['item']['props']['message_class'] ?? $messageClass;
492
493
			// Determine again if meeting processing is required after parsing
494
			if (!$requiresMeeting) {
495
				$requiresMeeting = class_match_prefix($messageClass, "IPM.Schedule.Meeting");
496
			}
497
498
			// Check for meeting request, do processing if necessary
499
			if ($requiresMeeting) {
500
				$req = new Meetingrequest($store, $message, $GLOBALS['mapisession']->getSession(), $this->directBookingMeetingRequest);
501
502
				try {
503
					if ($req->isMeetingRequestResponse($messageClass)) {
504
						if ($req->isLocalOrganiser()) {
505
							// We received a meeting request response, and we're the delegate/organiser
506
							$req->processMeetingRequestResponse();
507
						}
508
					}
509
					elseif ($req->isMeetingRequest($messageClass)) {
510
						if (!$req->isLocalOrganiser()) {
511
							if ($req->isMeetingOutOfDate()) {
512
								// we know that meeting is out of date so directly set this properties
513
								$data['item']['props']['meetingtype'] = mtgOutOfDate;
514
								$data['item']['props']['icon_index'] = 1033;
515
516
								// send update to maillistmodule that meeting request is updated with out of date flag
517
								$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
518
								$GLOBALS['bus']->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
519
							}
520
							else {
521
								/*
522
								 * if meeting request is not out of date then process it for the first time
523
								 * which will create corresponding appointment in the user's calendar
524
								 */
525
								$calItemEntryid = $req->doAccept(true, false, false);
526
								// we need to set timezone information for all-day events
527
								if (isset($data['item']['props']['alldayevent']) &&
528
									$data['item']['props']['alldayevent'] == true &&
529
									!empty($action["timezone_iana"]) &&
530
									is_string($calItemEntryid)) {
531
									try {
532
										$tzdef = mapi_ianatz_to_tzdef($action['timezone_iana']);
533
										$tzdefObj = $GLOBALS['entryid']->createTimezoneDefinitionObject($tzdef);
534
										$tzEffRuleIdx = getEffectiveTzreg($tzdefObj['rules']);
535
										if (!is_null($tzEffRuleIdx)) {
536
											$localStart = $data['item']['props']['appointment_startdate'] + $tzdefObj['rules'][$tzEffRuleIdx]['bias'] * 60;
537
											if (isDst($tzdefObj['rules'][$tzEffRuleIdx], $data['item']['props']['appointment_startdate'])) {
538
												$localStart += $tzdefObj['rules'][$tzEffRuleIdx]['dstbias'] * 60;
539
											}
540
											$duration = $data['item']['props']['appointment_duedate'] - $data['item']['props']['appointment_startdate'];
541
542
											$calItem = $GLOBALS['mapisession']->openMessage($calItemEntryid);
543
											mapi_setprops($calItem, [
544
												$this->properties['appointment_startdate'] => $localStart,
545
												$this->properties['appointment_duedate'] => $localStart + $duration,
546
												$this->properties['tzdefstart'] => $tzdef,
547
												$this->properties['tzdefend'] => $tzdef,
548
											]);
549
											mapi_savechanges($calItem);
550
											$data['item']['props']['appointment_startdate'] = $localStart;
551
											$data['item']['props']['appointment_duedate'] = $localStart + $duration;
552
										}
553
									}
554
									catch (Exception $e) {
555
										error_log(sprintf("Error setting timezone to an all-day event: %s", $e));
556
									}
557
								}
558
							}
559
560
							// Show user whether meeting request conflict with other appointment or not.
561
							$meetingConflicts = $req->isMeetingConflicting();
562
563
							/**
564
							 * if $meetingConflicts is boolean and true then its a normal meeting.
565
							 * if $meetingConflicts is integer then it indicates no of instances of a recurring meeting which conflicts with Calendar.
566
							 */
567
							if ($meetingConflicts !== false) {
568
								if ($meetingConflicts === true) {
569
									$data['item']['props']['conflictinfo'] = _('Conflicts with another appointment.');
570
								}
571
								else {
572
									$data['item']['props']['conflictinfo'] = sprintf(ngettext('%s occurrence of this recurring appointment conflicts with other appointment.', '%s occurrences of this recurring appointment conflicts with other appointments.', $meetingConflicts), $meetingConflicts);
573
								}
574
							}
575
						}
576
					}
577
					elseif ($req->isMeetingCancellation()) {
578
						$req->processMeetingCancellation();
579
					}
580
581
					if ($req->isInCalendar()) {
582
						$calendarItemProps = $this->getCalendarItemProps($req);
583
						if (!empty($calendarItemProps)) {
584
							$data['item']['props'] = array_merge($data['item']['props'], $calendarItemProps);
585
						}
586
					}
587
					else {
588
						$data['item']['props']['appointment_not_found'] = true;
589
					}
590
				}
591
				catch (MAPIException $e) {
592
					// if quota is exceeded or or we don't have permission to write in calendar folder than ignore the exception.
593
					if ($e->getCode() !== MAPI_E_STORE_FULL && $e->getCode() !== MAPI_E_NO_ACCESS) {
594
						// re-throw the exception if it is not one of quota/calendar permission.
595
						throw $e;
596
					}
597
				}
598
				if (!empty($data['item']['props']['appointment_recurring']) &&
599
					empty($data['item']['props']['appointment_recurring_pattern'])) {
600
					$recurr = new Recurrence($store, $message);
601
					$data['item']['props']['appointment_recurring_pattern'] = $recurr->saveRecurrencePattern();
602
				}
603
			}
604
			elseif (class_match_prefix($messageClass, "REPORT.IPM.NOTE.NDR")) {
605
				// check if this message is a NDR (mail)message, if so, generate a new body message
606
				$data['item']['props']['isHTML'] = false;
607
				$data['item']['props']['body'] = $this->getNDRbody($message);
608
			}
609
		}
610
611
		$userEntryId = '';
612
		if (isset($data['item']['props']['sent_representing_entryid'])) {
613
			$userEntryId = hex2bin($data['item']['props']['sent_representing_entryid']);
614
		}
615
		elseif (isset($data['item']['props']['sender_entryid'])) {
616
			$userEntryId = hex2bin($data['item']['props']['sender_entryid']);
617
		}
618
619
		// get user image saved in LDAP.
620
		if (!empty($userEntryId)) {
621
			$data['item']['props']['user_image'] = $GLOBALS['operations']->getCompressedUserImage($userEntryId);
622
		}
623
624
		// Allowing to hook in just before the data sent away to be sent to the client
625
		$GLOBALS['PluginManager']->triggerHook('server.module.itemmodule.open.after', [
626
			'moduleObject' => &$this,
627
			'store' => $store,
628
			'entryid' => $entryid,
629
			'action' => $action,
630
			'message' => &$message,
631
			'data' => &$data,
632
		]);
633
634
		// ugly workaround to show clip icon for the signed/encrypted emails
635
		if (isset($data['item']['props']['smime'])) {
636
			$data['item']['props']['hasattach'] = true;
637
			unset($data['item']['props']['hide_attachments']);
638
		}
639
640
		$this->addActionData('item', $data);
641
		$GLOBALS['bus']->addData($this->getResponseData());
642
	}
643
644
	/**
645
	 * Function which saves an item.
646
	 *
647
	 * @param object $store         MAPI Message Store Object
648
	 * @param string $parententryid parent entryid of the message
649
	 * @param mixed  $entryid       entryid of the message
650
	 * @param array  $action        the action data, sent by the client
651
	 * @param string $actionType    The action type which triggered this action
652
	 */
653
	public function save($store, $parententryid, $entryid, $action, $actionType = 'save') {
654
		$result = false;
655
656
		if (isset($action["props"])) {
657
			if (!$store) {
0 ignored issues
show
introduced by
$store is of type object, thus it always evaluated to true.
Loading history...
658
				$store = $GLOBALS['mapisession']->getDefaultMessageStore();
659
			}
660
			if (!$parententryid) {
661
				if (isset($action['props']['message_class'])) {
662
					$parententryid = $this->getDefaultFolderEntryID($store, $action['props']['message_class']);
663
				}
664
				else {
665
					$parententryid = $this->getDefaultFolderEntryID($store, '');
666
				}
667
			}
668
669
			if ($store && $parententryid) {
670
				$props = Conversion::mapXML2MAPI($this->properties, $action["props"]);
671
672
				$messageProps = []; // props returned from saveMessage
673
674
				// Save message
675
				if (!empty($props)) {
676
					$result = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, $props, $messageProps, [], !empty($action['attachments']) ? $action['attachments'] : []);
677
				}
678
679
				if ($result) {
680
					$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_SAVE, $messageProps);
681
682
					$this->addActionData("update", ["item" => Conversion::mapMAPI2XML($this->properties, $messageProps)]);
683
					$GLOBALS["bus"]->addData($this->getResponseData());
684
				}
685
				else {
686
					$this->sendFeedback(false);
687
				}
688
			}
689
		}
690
	}
691
692
	/**
693
	 * Function which deletes an item.
694
	 *
695
	 * @param object $store         MAPI Message Store Object
696
	 * @param string $parententryid parent entryid of the message
697
	 * @param string $entryid       entryid of the message
698
	 * @param array  $action        the action data, sent by the client
699
	 */
700
	public function delete($store, $parententryid, $entryid, $action) {
701
		if (!$store || !$parententryid || !$entryid) {
0 ignored issues
show
introduced by
$store is of type object, thus it always evaluated to true.
Loading history...
702
			return;
703
		}
704
		$props = [];
705
		$props[PR_PARENT_ENTRYID] = $parententryid;
706
		$props[PR_ENTRYID] = $entryid;
707
708
		$storeprops = mapi_getprops($store, [PR_ENTRYID]);
709
		$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
710
711
		$soft = $action['message_action']['soft_delete'] ?? false;
712
		$unread = $action['message_action']['non_read_notify'] ?? false;
713
		$result = $GLOBALS["operations"]->deleteMessages($store, $parententryid, $entryid, $soft, $unread);
714
		if ($result) {
715
			$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_DELETE, $props);
716
			$this->sendFeedback(true);
717
		}
718
	}
719
720
	/**
721
	 * Function which returns the entryid of a default folder.
722
	 *
723
	 * @param object $store        MAPI Message Store Object
724
	 * @param string $messageClass the class of the folder
725
	 *
726
	 * @return string entryid of a default folder, false if not found
727
	 */
728
	public function getDefaultFolderEntryID($store, $messageClass) {
729
		$entryid = false;
730
731
		if ($store) {
0 ignored issues
show
introduced by
$store is of type object, thus it always evaluated to true.
Loading history...
732
			$rootcontainer = mapi_msgstore_openentry($store);
733
			$rootcontainerprops = mapi_getprops($rootcontainer, [PR_IPM_DRAFTS_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID]);
734
735
			switch ($messageClass) {
736
				case "IPM.Appointment":
737
					if (isset($rootcontainerprops[PR_IPM_APPOINTMENT_ENTRYID])) {
738
						$entryid = $rootcontainerprops[PR_IPM_APPOINTMENT_ENTRYID];
739
					}
740
					break;
741
742
				case "IPM.Contact":
743
				case "IPM.DistList":
744
					if (isset($rootcontainerprops[PR_IPM_CONTACT_ENTRYID])) {
745
						$entryid = $rootcontainerprops[PR_IPM_CONTACT_ENTRYID];
746
					}
747
					break;
748
749
				case "IPM.StickyNote":
750
					if (isset($rootcontainerprops[PR_IPM_NOTE_ENTRYID])) {
751
						$entryid = $rootcontainerprops[PR_IPM_NOTE_ENTRYID];
752
					}
753
					break;
754
755
				case "IPM.Task":
756
					if (isset($rootcontainerprops[PR_IPM_TASK_ENTRYID])) {
757
						$entryid = $rootcontainerprops[PR_IPM_TASK_ENTRYID];
758
					}
759
					break;
760
761
				default:
762
					if (isset($rootcontainerprops[PR_IPM_DRAFTS_ENTRYID])) {
763
						$entryid = $rootcontainerprops[PR_IPM_DRAFTS_ENTRYID];
764
					}
765
					break;
766
			}
767
		}
768
769
		return $entryid;
770
	}
771
772
	/**
773
	 * Function which copies or moves one or more items.
774
	 *
775
	 * @param resource $store         MAPI Message Store Object
776
	 * @param string   $parententryid entryid of the folder
777
	 * @param mixed    $entryids      list of entryids which will be copied or moved (in binary format)
778
	 * @param array    $action        the action data, sent by the client
779
	 */
780
	public function copy($store, $parententryid, $entryids, $action) {
781
		$result = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
782
783
		if ($store && $parententryid && $entryids) {
0 ignored issues
show
introduced by
$store is of type resource, thus it always evaluated to false.
Loading history...
784
			$dest_store = $store;
785
			if (isset($action["message_action"]["destination_store_entryid"])) {
786
				$dest_storeentryid = hex2bin($action["message_action"]["destination_store_entryid"]);
787
				$dest_store = $GLOBALS["mapisession"]->openMessageStore($dest_storeentryid);
788
			}
789
790
			$dest_folderentryid = false;
791
			if (isset($action["message_action"]["destination_parent_entryid"])) {
792
				$dest_folderentryid = hex2bin($action["message_action"]["destination_parent_entryid"]);
793
			}
794
795
			$moveMessages = false;
796
			if (isset($action["message_action"]["action_type"]) && $action["message_action"]["action_type"] == "move") {
797
				$moveMessages = true;
798
			}
799
800
			// if item has some set of props that need to be saved into the newly copied/moved item
801
			$copyProps = [];
802
			if (isset($action["message_action"]["dropmodifications"])) {
803
				$copyProps = Conversion::mapXML2MAPI($this->properties, $action["message_action"]["dropmodifications"]);
804
			}
805
806
			// if item has some changes made before choosing different calendar from create-in dropdown
807
			if (isset($action["props"]) && !empty($action["props"])) {
808
				$copyProps = Conversion::mapXML2MAPI($this->properties, $action["props"]);
809
			}
810
811
			$props = [];
812
			$props[PR_PARENT_ENTRYID] = $parententryid;
813
			$props[PR_ENTRYID] = $entryids;
814
815
			$storeprops = mapi_getprops($store, [PR_ENTRYID]);
816
			$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
817
818
			$skipCopyProperties = [];
819
			if (isset($action["message_action"]["unset_Private"])) {
820
				if ($moveMessages) {
821
					array_push($skipCopyProperties, $this->properties["private"], $this->properties["sensitivity"]);
822
				}
823
				else {
824
					array_push($this->skipCopyProperties, $this->properties["private"], $this->properties["sensitivity"]);
825
				}
826
			}
827
828
			$result = $GLOBALS["operations"]->copyMessages($store, $parententryid, $dest_store, $dest_folderentryid, $entryids, $moveMessages ? $skipCopyProperties : $this->skipCopyProperties, $moveMessages, $copyProps);
829
830
			if ($result) {
831
				if ($moveMessages) {
832
					$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_DELETE, $props);
833
				}
834
835
				// Delete the PR_ENTRYID, the copied or moved message has a new entryid,
836
				// and at this time we have no idea what that might be. So make sure
837
				// we unset it, otherwise the notification handlers get weird ideas
838
				// and could reset the PR_PARENT_ENTRYID to the old folder again.
839
				unset($props[PR_ENTRYID]);
840
				$props[PR_PARENT_ENTRYID] = $dest_folderentryid;
841
				$props[PR_STORE_ENTRYID] = $dest_storeentryid;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $dest_storeentryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
842
				$GLOBALS["bus"]->notify(bin2hex($dest_folderentryid), TABLE_SAVE, $props);
843
			}
844
845
			$this->sendFeedback($result, []);
846
		}
847
	}
848
849
	/**
850
	 * Function returns correspondent calendar item's properties attached
851
	 * with the meeting request/response/cancellation.
852
	 *
853
	 * @param Meetingrequest $meetingRequestObject the meeting request object
854
	 *                                             using which function will fetch meeting request properties and return them
855
	 */
856
	public function getCalendarItemProps($meetingRequestObject) {
857
		$calendarItem = $meetingRequestObject->getCorrespondentCalendarItem();
858
		$props = [];
859
		if ($calendarItem !== false) {
860
			$calendarItemProps = mapi_getprops($calendarItem, [PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $meetingRequestObject->proptags['updatecounter'], $meetingRequestObject->proptags['goid']]);
861
862
			// Store calendar item's necessary properties in props array.
863
			$props['appointment_store_entryid'] = bin2hex((string) $calendarItemProps[PR_STORE_ENTRYID]);
864
			$props['appointment_parent_entryid'] = bin2hex((string) $calendarItemProps[PR_PARENT_ENTRYID]);
865
			$props['appointment_entryid'] = bin2hex((string) $calendarItemProps[PR_ENTRYID]);
866
867
			$props['appointment_updatecounter'] = $calendarItemProps[$meetingRequestObject->proptags['updatecounter']] ?? 0;
868
869
			$messageProps = mapi_getprops($meetingRequestObject->message, [$meetingRequestObject->proptags['goid']]);
870
871
			$basedate = $meetingRequestObject->getBasedateFromGlobalID($messageProps[$meetingRequestObject->proptags['goid']]);
872
873
			if ($basedate !== false) {
874
				$props['appointment_basedate'] = $basedate;
875
876
				// if basedate is provided then it is exception, so get update counter of the exception
877
				$exception = $meetingRequestObject->getExceptionItem($calendarItem, $basedate);
878
879
				if ($exception !== false) {
880
					// we are able to find the exception then get updatecounter
881
					$exceptionProps = mapi_getprops($exception, [$meetingRequestObject->proptags['updatecounter']]);
882
					$props['appointment_updatecounter'] = $exceptionProps[$meetingRequestObject->proptags['updatecounter']] ?? 0;
883
				}
884
			}
885
886
			if ($meetingRequestObject->isMeetingRequestResponse()) {
887
				$props['meeting_updated'] = $meetingRequestObject->isMeetingUpdated($basedate);
888
			}
889
890
			return $props;
891
		}
892
893
		return false;
894
	}
895
896
	/**
897
	 * Get a text body for a Non-Delivery report.
898
	 *
899
	 * This function reads the necessary properties from the passed message and constructs
900
	 * a user-readable NDR message from those properties
901
	 *
902
	 * @param mapimessage $message The NDR message to read the information from
0 ignored issues
show
Bug introduced by
The type mapimessage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
903
	 *
904
	 * @return string NDR body message as plaintext message
905
	 */
906
	public function getNDRbody($message) {
907
		$message_props = mapi_getprops($message, [PR_ORIGINAL_SUBJECT, PR_ORIGINAL_SUBMIT_TIME, PR_BODY]);
908
		$body = '';
909
910
		// use PR_BODY if it's there, otherwise create a recipient failed message
911
		if (isset($message_props[PR_BODY]) || propIsError(PR_BODY, $message_props) == MAPI_E_NOT_ENOUGH_MEMORY) {
912
			$body = mapi_openproperty($message, PR_BODY);
913
		}
914
915
		if (empty($body)) {
916
			$body = _("Your message did not reach some or all of the intended recipients") . "\n\n";
917
			$body .= "\t" . _("Subject") . ": " . $message_props[PR_ORIGINAL_SUBJECT] . "\n";
918
			$body .= "\t" . _("Sent") . ":    " . date(DATE_RFC2822, $message_props[PR_ORIGINAL_SUBMIT_TIME]) . "\n\n";
919
			$body .= _("The following recipient(s) could not be reached") . ":\n";
920
921
			$recipienttable = mapi_message_getrecipienttable($message);
922
			$recipientrows = mapi_table_queryallrows($recipienttable, [PR_DISPLAY_NAME, PR_REPORT_TIME, PR_REPORT_TEXT]);
923
			foreach ($recipientrows as $recipient) {
924
				$body .= "\n\t" . $recipient[PR_DISPLAY_NAME] . " on " . date(DATE_RFC2822, $recipient[PR_REPORT_TIME]) . "\n";
925
				$body .= "\t\t" . $recipient[PR_REPORT_TEXT] . "\n";
926
			}
927
		}
928
929
		return $body;
930
	}
931
932
	/**
933
	 * Send a meeting cancellation.
934
	 *
935
	 * This function sends a meeting cancellation for the meeting references by the passed entryid. It
936
	 * will send the meeting cancellation and move the item itself to the waste basket.
937
	 *
938
	 * @param mapistore $store                       The store in which the meeting request resides
0 ignored issues
show
Bug introduced by
The type mapistore was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
939
	 * @param string    $entryid                     entryid of the appointment for which the cancellation should be sent
940
	 * @param object    $action                      data sent by client
941
	 * @param bool      $directBookingMeetingRequest Indicates if a Meeting Request should use direct booking or not
942
	 */
943
	public function cancelInvitation($store, $entryid, $action, $directBookingMeetingRequest) {
944
		$message = $GLOBALS['operations']->openMessage($store, $entryid);
945
946
		// @TODO move this to meeting request class ?
947
		$req = new Meetingrequest($store, $message, $GLOBALS['mapisession']->getSession(), $directBookingMeetingRequest);
948
949
		// Update extra body information
950
		if (isset($action['message_action']['meetingTimeInfo']) && !empty($action['message_action']['meetingTimeInfo'])) {
951
			$req->setMeetingTimeInfo($action["message_action"]['meetingTimeInfo'],
952
				$action["message_action"]['mti_html'] ?? false);
953
			unset($action["message_action"]['meetingTimeInfo']);
954
		}
955
956
		// get basedate from action data and pass to meeting request class
957
		$basedate = !empty($action['basedate']) ? $action['basedate'] : false;
958
959
		$req->doCancelInvitation($basedate);
960
961
		if ($basedate !== false) {
962
			// if basedate is specified then we have created exception in recurring meeting request
963
			// so send notification of creation of exception
964
			$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
965
			$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
966
		}
967
		else {
968
			// for normal/recurring meetings send delete notification
969
			$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
970
			$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
971
		}
972
	}
973
974
	/**
975
	 * Remove all appointments for a certain meeting request.
976
	 *
977
	 * This function searches the default calendar for all meeting requests for the specified
978
	 * meeting. All those appointments are then removed.
979
	 *
980
	 * @param mapistore $store                       Mapi store in which the meeting request and the calendar reside
981
	 * @param string    $entryid                     Entryid of the meeting request or appointment for which all items should be deleted
982
	 * @param string    $basedate                    if specified contains starttime of day of an occurrence
983
	 * @param bool      $directBookingMeetingRequest Indicates if a Meeting Request should use direct booking or not
984
	 */
985
	public function removeFromCalendar($store, $entryid, $basedate, $directBookingMeetingRequest) {
986
		$message = $GLOBALS["operations"]->openMessage($store, $entryid);
987
988
		$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $directBookingMeetingRequest);
989
990
		$req->doRemoveFromCalendar($basedate);
991
992
		// Notify the bus that the message has been deleted
993
		$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
994
		$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), $basedate ? TABLE_SAVE : TABLE_DELETE, $messageProps);
995
	}
996
}
997