Passed
Push — master ( d44c66...31a96e )
by
unknown
16:00 queued 12s
created

AppointmentItemModule::handleException()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
c 0
b 0
f 0
nc 5
nop 6
dl 0
loc 22
rs 9.4888
1
<?php
2
3
	/**
4
	 * Appointment ItemModule
5
	 * Module which openes, creates, saves and deletes an item. It
6
	 * extends the Module class.
7
	 */
8
	class AppointmentItemModule extends ItemModule {
9
		/**
10
		 * @var bool|string client timezone definition
11
		 */
12
		protected $tzdef;
13
14
		/**
15
		 * @var array|bool client timezone definition array
16
		 */
17
		protected $tzdefObj;
18
19
		/**
20
		 * @var mixed client timezone effective rule id
21
		 */
22
		protected $tzEffRuleIdx;
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
			parent::__construct($id, $data);
32
33
			$this->properties = $GLOBALS['properties']->getAppointmentProperties();
0 ignored issues
show
Bug Best Practice introduced by
The property properties does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
34
35
			$this->plaintext = true;
36
			$this->skipCopyProperties = [
37
				$this->properties['goid'],
38
				$this->properties['goid2'],
39
				$this->properties['request_sent'],
40
				PR_OWNER_APPT_ID,
41
			];
42
43
			$this->tzdef = false;
44
			$this->tzdefObj = false;
45
		}
46
47
		public function open($store, $entryid, $action) {
48
			if ($store && $entryid) {
49
				$data = [];
50
51
				$message = $GLOBALS['operations']->openMessage($store, $entryid);
52
53
				if (empty($message)) {
54
					return;
55
				}
56
57
				// Open embedded message if requested
58
				$attachNum = !empty($action['attach_num']) ? $action['attach_num'] : false;
59
				if ($attachNum) {
60
					// get message props of sub message
61
					$parentMessage = $message;
62
					$message = $GLOBALS['operations']->openMessage($store, $entryid, $attachNum);
63
64
					if (empty($message)) {
65
						return;
66
					}
67
68
					$data['item'] = $GLOBALS['operations']->getEmbeddedMessageProps($store, $message, $this->properties, $parentMessage, $attachNum);
69
				}
70
				else {
71
					// add all standard properties from the series/normal message
72
					$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $this->properties, $this->plaintext);
73
				}
74
75
				if (!empty($action["timezone_iana"])) {
76
					try {
77
						$this->tzdef = mapi_ianatz_to_tzdef($action['timezone_iana']);
78
					}
79
					catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
80
					}
81
				}
82
83
				// if appointment is recurring then only we should get properties of occurrence if basedate is supplied
84
				if ($data['item']['props']['recurring'] === true) {
85
					if (!empty($action['basedate'])) {
86
						// check for occurrence/exception
87
						$basedate = $action['basedate'];
88
89
						$recur = 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...
90
91
						$exceptionatt = $recur->getExceptionAttachment($basedate);
92
93
						// Single occurrences are never recurring
94
						$data['item']['props']['recurring'] = false;
95
96
						if ($exceptionatt) {
97
							// Existing exception (open existing item, which includes basedate)
98
							$exceptionattProps = mapi_getprops($exceptionatt, [PR_ATTACH_NUM]);
99
							$exception = mapi_attach_openobj($exceptionatt, 0);
100
101
							// overwrite properties with the ones from the exception
102
							$exceptionProps = $GLOBALS['operations']->getMessageProps($store, $exception, $this->properties, $this->plaintext);
103
104
							/*
105
							 * If recurring item has set reminder to true then
106
							 * all occurrences before the 'flagdueby' value(of recurring item)
107
							 * should not show that reminder is set.
108
							 */
109
							if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
110
								$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
111
112
								if ($flagDueByDay > $basedate) {
113
									$exceptionProps['props']['reminder'] = false;
114
								}
115
							}
116
117
							// The properties must be merged, if the recipients or attachments are present in the exception
118
							// then that list should be used. Otherwise the list from the series must be applied (this
119
							// corresponds with OL2007).
120
							// @FIXME getMessageProps should not return empty string if exception doesn't contain body
121
							// by this change we can handle a situation where user has set empty string in the body explicitly
122
							if (!empty($exceptionProps['props']['body']) || !empty($exceptionProps['props']['html_body'])) {
123
								if (!empty($exceptionProps['props']['body'])) {
124
									$data['item']['props']['body'] = $exceptionProps['props']['body'];
125
								}
126
127
								if (!empty($exceptionProps['props']['html_body'])) {
128
									$data['item']['props']['html_body'] = $exceptionProps['props']['html_body'];
129
								}
130
131
								$data['item']['props']['isHTML'] = $exceptionProps['props']['isHTML'];
132
							}
133
							// remove properties from $exceptionProps so array_merge will not overwrite it
134
							unset($exceptionProps['props']['html_body'], $exceptionProps['props']['body'], $exceptionProps['props']['isHTML']);
135
136
							$data['item']['props'] = array_merge($data['item']['props'], $exceptionProps['props']);
137
							if (isset($exceptionProps['recipients'])) {
138
								$data['item']['recipients'] = $exceptionProps['recipients'];
139
							}
140
141
							if (isset($exceptionProps['attachments'])) {
142
								$data['item']['attachments'] = $exceptionProps['attachments'];
143
							}
144
145
							// Make sure we are using the passed basedate and not something wrong in the opened item
146
							$data['item']['props']['basedate'] = $basedate;
147
							$data['item']['attach_num'] = [$exceptionattProps[PR_ATTACH_NUM]];
148
						}
149
						elseif ($recur->isDeleteException($basedate)) {
150
							// Exception is deleted, should not happen, but if it the case then give error
151
							$this->sendFeedback(
152
								false,
153
								[
154
									'type' => ERROR_ZARAFA,
155
									'info' => [
156
										'original_message' => _('Could not open occurrence.'),
157
										'display_message' => _('Could not open occurrence, specific occurrence is probably deleted.'),
158
									],
159
								]
160
							);
161
162
							return;
163
						}
164
						else {
165
							// opening an occurrence of a recurring series (same as normal open, but add basedate, startdate and enddate)
166
							$data['item']['props']['basedate'] = $basedate;
167
							$data['item']['props']['startdate'] = $recur->getOccurrenceStart($basedate);
168
							$data['item']['props']['duedate'] = $recur->getOccurrenceEnd($basedate);
169
							$data['item']['props']['commonstart'] = $data['item']['props']['startdate'];
170
							$data['item']['props']['commonend'] = $data['item']['props']['duedate'];
171
							unset($data['item']['props']['reminder_time']);
172
173
							/*
174
							 * If recurring item has set reminder to true then
175
							 * all occurrences before the 'flagdueby' value(of recurring item)
176
							 * should not show that reminder is set.
177
							 */
178
							if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $exceptionProps does not exist. Did you maybe mean $exception?
Loading history...
179
								$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
180
181
								if ($flagDueByDay > $basedate) {
182
									$exceptionProps['props']['reminder'] = false;
183
								}
184
							}
185
						}
186
					}
187
					else {
188
						// Opening a recurring series, get the recurrence information
189
						$recur = new Recurrence($store, $message);
190
						$recurpattern = $recur->getRecurrence();
191
						$tz = $recur->tz; // no function to do this at the moment
192
193
						// Add the recurrence pattern to the data
194
						if (isset($recurpattern) && is_array($recurpattern)) {
195
							$data['item']['props'] += $recurpattern;
196
						}
197
198
						// Add the timezone information to the data
199
						if (isset($tz) && is_array($tz)) {
200
							$data['item']['props'] += $tz;
201
						}
202
					}
203
				}
204
205
				// Fix for all-day events which have a different timezone than the user's browser
206
				if ($data['item']['props']['alldayevent'] == 1 && $this->tzdef !== false) {
207
					$this->processAllDayItem($store, $data['item'], $message);
208
				}
209
210
				// Send the data
211
				$this->addActionData('item', $data);
212
				$GLOBALS['bus']->addData($this->getResponseData());
213
			}
214
		}
215
216
		/**
217
		 * Function does customization of exception based on module data.
218
		 * like, here it will generate display message based on actionType
219
		 * for particular exception.
220
		 *
221
		 * @param object     $e             Exception object
222
		 * @param string     $actionType    the action type, sent by the client
223
		 * @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...
224
		 * @param string     $parententryid parent entryid of the message
225
		 * @param string     $entryid       entryid of the message
226
		 * @param array      $action        the action data, sent by the client
227
		 */
228
		public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
229
			if (is_null($e->displayMessage)) {
230
				switch ($actionType) {
231
					case "save":
232
						if ($e->getCode() == MAPI_E_NO_ACCESS) {
233
							$message = mapi_msgstore_openentry($store, $entryid);
234
							$messageProps = mapi_getprops($message, [PR_MESSAGE_CLASS, PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
235
							$messageClass = $messageProps[PR_MESSAGE_CLASS];
236
237
							$text = $messageClass !== "IPM.Appointment" ? _('a meeting request') : _('an appointment');
238
							$msg = _('You have insufficient privileges to move ' . $text . ' in this calendar. The calendar owner can set these using the \'permissions\'-tab of the folder properties (right click the calendar folder > properties > permissions)');
239
240
							$e->setDisplayMessage($msg);
241
							$e->setTitle(_('Insufficient privileges'));
242
243
							// Need this notification to refresh the calendar.
244
							$GLOBALS['bus']->notify(bin2hex($parententryid), TABLE_DELETE, $messageProps);
0 ignored issues
show
Bug introduced by
It seems like $parententryid can also be of type null; however, parameter $string of bin2hex() 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

244
							$GLOBALS['bus']->notify(bin2hex(/** @scrutinizer ignore-type */ $parententryid), TABLE_DELETE, $messageProps);
Loading history...
245
						}
246
						break;
247
				}
248
			}
249
			parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
250
		}
251
252
		/**
253
		 * Save the give appointment or meeting request to the calendar.
254
		 *
255
		 * @param mapistore $store         MAPI store of the message
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...
256
		 * @param string    $parententryid Parent entryid of the message (folder entryid, NOT message entryid)
257
		 * @param string    $entryid       entryid of the message
258
		 * @param array     $action        Action array containing json request
259
		 * @param string    $actionType    The action type which triggered this action
260
		 */
261
		public function save($store, $parententryid, $entryid, $action, $actionType = 'save') {
262
			$result = false;
263
264
			// Save appointment (saveAppointment takes care of creating/modifying exceptions to recurring
265
			// items if necessary)
266
			$messageProps = $GLOBALS['operations']->saveAppointment($store, $entryid, $parententryid, $action, $actionType, $this->directBookingMeetingRequest);
267
268
			// Notify the bus if the save was OK
269
			if ($messageProps && !(is_array($messageProps) && isset($messageProps['error'])) && !isset($messageProps['remindertimeerror'])) {
270
				$GLOBALS['bus']->notify(bin2hex($parententryid), TABLE_SAVE, $messageProps);
271
				$result = true;
272
			}
273
274
			$errorMsg = false;
275
			if (!$result && isset($messageProps['remindertimeerror']) && !$messageProps['remindertimeerror']) {
276
				$errorMsg = _('Cannot set a reminder to appear before the previous occurrence. Reset reminder to save the change');
277
			}
278
			elseif (isset($messageProps['isexceptionallowed']) && $messageProps['isexceptionallowed'] === false) {
279
				$errorMsg = _('Two occurrences cannot occur on the same day');
280
			}
281
			elseif (is_array($messageProps) && isset($messageProps['error'])) {
282
				switch ($messageProps['error']) {
283
					case 1:
284
						$errorMsg = sprintf(_('You marked \'%s\' as a resource. You cannot schedule a meeting with \'%s\' because you do not have the appropriate permissions for that account. Either enter the name as a required or optional attendee or talk to your administrator about giving you permission to schedule \'%s\'.'), $messageProps['displayname'], $messageProps['displayname'], $messageProps['displayname']);
285
						break;
286
287
					case 2:
288
						$errorMsg = sprintf(_('\'%s\' has declined your meeting because \'%s\' does not automatically accept meeting requests.'), $messageProps['displayname'], $messageProps['displayname']);
289
						break;
290
291
					case 3:
292
						$errorMsg = sprintf(_('\'%s\' has declined your meeting because it is recurring. You must book each meeting separately with this resource.'), $messageProps['displayname']);
293
						break;
294
295
					case 4:
296
						$errorMsg = sprintf(_('\'%s\' is already booked for this specified time. You must use another time or find another resource.'), $messageProps['displayname']);
297
						break;
298
299
					default:
300
						$errorMsg = _('Meeting was not scheduled.');
301
						break;
302
				}
303
			}
304
			else {
305
				// Recurring but non-existing exception (same as normal open, but add basedate, startdate and enddate)
306
				$data = [];
307
				if ($result) {
308
					$data = Conversion::mapMAPI2XML($this->properties, $messageProps);
309
310
					// Get recipient information from the saved appointment to update client side
311
					// according to the latest recipient related changes only if changes requested from client.
312
					$savedAppointment = $GLOBALS['operations']->openMessage($store, $messageProps[PR_ENTRYID]);
313
					if (!empty($action['recipients'])) {
314
						$recipients = $GLOBALS["operations"]->getRecipientsInfo($savedAppointment);
315
						if (!empty($recipients)) {
316
							$data["recipients"] = [
317
								"item" => $recipients,
318
							];
319
						}
320
					}
321
322
					// Get attachments information from the saved appointment to update client side
323
					// according to the latest attachments related changes only if changes requested from client.
324
					if (!empty($action['attachments'])) {
325
						$attachments = $GLOBALS["operations"]->getAttachmentsInfo($savedAppointment);
326
						if (!empty($attachments)) {
327
							$data["attachments"] = [
328
								"item" => $attachments,
329
							];
330
						}
331
					}
332
333
					$data['action_response'] = [
334
						'resources_booked' => $this->directBookingMeetingRequest,
335
					];
336
337
					if (isset($action['message_action'], $action['message_action']['paste'])) {
338
						$data['action_response']['resources_pasted'] = true;
339
					}
340
				}
341
				else {
342
					if (!empty($action['message_action']['send'])) {
343
						$errorMsg = _('Meeting could not be sent.');
344
					}
345
					else {
346
						$errorMsg = _('Meeting could not be saved.');
347
					}
348
				}
349
			}
350
351
			if ($errorMsg === false) {
352
				$this->addActionData('update', ['item' => $data]);
353
				$GLOBALS['bus']->addData($this->getResponseData());
354
			}
355
			else {
356
				$this->sendFeedback(false, [
357
					'type' => ERROR_ZARAFA,
358
					'info' => [
359
						'display_message' => $errorMsg,
360
					],
361
				]);
362
			}
363
		}
364
365
		/**
366
		 * Processes an all-day item and calculates the correct starttime if necessary.
367
		 *
368
		 * @param object $store
369
		 * @param array  $calendaritem
370
		 * @param object $message
371
		 */
372
		private function processAllDayItem($store, &$calendaritem, $message) {
0 ignored issues
show
Unused Code introduced by
The parameter $message is not used and could be removed. ( Ignorable by Annotation )

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

372
		private function processAllDayItem($store, &$calendaritem, /** @scrutinizer ignore-unused */ $message) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $store is not used and could be removed. ( Ignorable by Annotation )

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

372
		private function processAllDayItem(/** @scrutinizer ignore-unused */ $store, &$calendaritem, $message) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
373
			if (!isset($calendaritem['props']['tzdefstart'])) {
374
				return;
375
			}
376
377
			// Compare the timezone definitions of the client and the appointment.
378
			// Further processing is only required if they don't match.
379
			if (!$GLOBALS['entryid']->compareEntryIds($this->tzdef, $calendaritem['props']['tzdefstart'])) {
380
				if ($this->tzdefObj === false) {
381
					$this->tzdefObj = $GLOBALS['entryid']->createTimezoneDefinitionObject($this->tzdef);
382
				}
383
				$this->tzEffRuleIdx = getEffectiveTzreg($this->tzdefObj['rules']);
384
385
				$appTzDefStart = $GLOBALS['entryid']->createTimezoneDefinitionObject(hex2bin($calendaritem['props']['tzdefstart']));
386
387
				// Find TZRULE_FLAG_EFFECTIVE_TZREG rule for the appointment's timezone
388
				$appTzEffRuleIdx = getEffectiveTzreg($appTzDefStart['rules']);
389
390
				if (!is_null($this->tzEffRuleIdx) && !is_null($appTzEffRuleIdx)) {
391
					// first apply the bias of the appointment timezone and the bias of the browser
392
					$localStart = $calendaritem['props']['startdate'] - $appTzDefStart['rules'][$appTzEffRuleIdx]['bias'] * 60 + $this->tzdefObj['rules'][$this->tzEffRuleIdx]['bias'] * 60;
393
					if (isDst($appTzDefStart['rules'][$appTzEffRuleIdx], $calendaritem['props']['startdate'])) {
394
						$localStart -= $appTzDefStart['rules'][$appTzEffRuleIdx]['dstbias'] * 60;
395
					}
396
					if (isDst($this->tzdefObj['rules'][$this->tzEffRuleIdx], $calendaritem['props']['startdate'])) {
397
						$localStart += $this->tzdefObj['rules'][$this->tzEffRuleIdx]['dstbias'] * 60;
398
					}
399
					$duration = $calendaritem['props']['duedate'] - $calendaritem['props']['startdate'];
400
					$calendaritem['props']['startdate'] = $calendaritem['props']['commonstart'] = $localStart;
401
					$calendaritem['props']['duedate'] = $calendaritem['props']['commonend'] = $localStart + $duration;
402
				}
403
			}
404
		}
405
	}
406