ItemModule   F
last analyzed

Complexity

Total Complexity 182

Size/Duplication

Total Lines 984
Duplicated Lines 0 %

Importance

Changes 10
Bugs 4 Features 1
Metric Value
eloc 491
dl 0
loc 984
rs 2
c 10
b 4
f 1
wmc 182

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
F execute() 0 252 53
D handleException() 0 124 31
A getNDRbody() 0 24 5
A cancelInvitation() 0 27 5
F copy() 0 66 16
A delete() 0 17 5
F open() 0 199 37
A getCalendarItemProps() 0 38 5
C getDefaultFolderEntryID() 0 42 12
B save() 0 34 10
A removeFromCalendar() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like ItemModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ItemModule, and based on these observations, apply Extract Interface, too.

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
									unset($action["message_action"]['meetingTimeInfo']);
111
								}
112
113
								// sendResponse flag if it is set then send the mail response to the organzer.
114
								$sendResponse = true;
115
								if (isset($action["message_action"]["sendResponse"]) && $action["message_action"]["sendResponse"] == false) {
116
									$sendResponse = false;
117
								}
118
119
								// @FIXME: fix body
120
								$body = false;
121
								if (isset($action["props"]["isHTML"]) && $action["props"]["isHTML"] === true) {
122
									$body = $action["props"]["html_body"] ?? false;
123
								}
124
								else {
125
									$body = $action["props"]["body"] ?? false;
126
								}
127
128
								if ($action["message_action"]["action_type"] == "acceptMeetingRequest") {
129
									$tentative = $action["message_action"]["responseType"] === olResponseTentative;
130
									$newProposedStartTime = $action["message_action"]["proposed_starttime"] ?? false;
131
									$newProposedEndTime = $action["message_action"]["proposed_endtime"] ?? false;
132
133
									// We are accepting MR from preview-read-mail so set delete the actual mail flag.
134
									$delete = $req->isMeetingRequest($originalMessageProps[PR_MESSAGE_CLASS]);
135
136
									$req->doAccept($tentative, $sendResponse, $delete, $newProposedStartTime, $newProposedEndTime, $body, true, $store, $basedate);
137
								}
138
								else {
139
									$delete = $req->doDecline($sendResponse, $basedate, $body);
140
								}
141
142
								/**
143
								 * Now if the item is the Meeting Request that was sent to the attendee
144
								 * it is removed when the user has clicked on Accept/Decline. If the
145
								 * item is the appointment in the calendar it will not be moved. To only
146
								 * notify the bus when the item is a Meeting Request we are going to
147
								 * check the PR_MESSAGE_CLASS and see if it is "IPM.Meeting*".
148
								 */
149
								$messageProps = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
150
151
								// if opened appointment is exception then it will add
152
								// the attach_num and basedate in messageProps.
153
								if (isset($attach_num)) {
154
									$messageProps[PR_ATTACH_NUM] = [$attach_num];
155
									$messageProps[$this->properties["basedate"]] = $basedate;
156
								}
157
158
								if ($delete) {
159
									// send TABLE_DELETE event because the message has moved
160
									$this->sendFeedback(true);
161
									$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
162
								}
163
								else {
164
									$this->addActionData("update", ["item" => Conversion::mapMAPI2XML($this->properties, $messageProps)]);
165
									$GLOBALS["bus"]->addData($this->getResponseData());
166
167
									// send TABLE_SAVE event because an occurrence is deleted
168
									$GLOBALS["bus"]->notify(bin2hex((string) $messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
169
								}
170
171
								break;
172
173
							case "acceptTaskRequest":
174
							case "declineTaskRequest":
175
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
176
177
								if (isset($action["props"]) && !empty($action["props"])) {
178
									$properties = $GLOBALS["properties"]->getTaskProperties();
179
									mapi_setprops($message, Conversion::mapXML2MAPI($properties, $action["props"]));
180
									mapi_savechanges($message);
181
								}
182
								// The task may be a delegated task, do an update if needed (will fail for non-delegated tasks)
183
								$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...
184
								$isAccept = $action["message_action"]["action_type"] == "acceptTaskRequest";
185
								if (isset($action["message_action"]["task_comments_info"]) && !empty($action["message_action"]["task_comments_info"])) {
186
									$tr->setTaskCommentsInfo($action["message_action"]["task_comments_info"]);
187
								}
188
								if ($isAccept) {
189
									$result = $tr->doAccept();
190
								}
191
								else {
192
									$result = $tr->doDecline();
193
								}
194
195
								$this->sendFeedback(true);
196
								if ($result !== false) {
197
									$GLOBALS["bus"]->notify(bin2hex((string) $result[PR_PARENT_ENTRYID]), TABLE_DELETE, $result);
198
								}
199
200
								$props = mapi_getprops($message, [PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID]);
201
								if (!$tr->isTaskRequest()) {
202
									unset($props[PR_MESSAGE_CLASS]);
203
									$GLOBALS["bus"]->notify(bin2hex((string) $props[PR_PARENT_ENTRYID]), $isAccept ? TABLE_SAVE : TABLE_DELETE, $props);
204
								}
205
								break;
206
207
							case "copy":
208
							case "move":
209
								$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

209
								$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

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

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

270
								$this->delete($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
271
								break;
272
273
							default:
274
								// Deleting an occurrence means that we have to save the message to
275
								// generate an exception. So when the basedate is provided, we actually
276
								// perform a save rather then delete.
277
								if (isset($action['basedate']) && !empty($action['basedate'])) {
278
									$this->save($store, $parententryid, $entryid, $action, "delete");
0 ignored issues
show
Unused Code introduced by
The call to ItemModule::save() has too many arguments starting with 'delete'. ( Ignorable by Annotation )

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

278
									$this->/** @scrutinizer ignore-call */ 
279
                save($store, $parententryid, $entryid, $action, "delete");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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