Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

AppointmentListModule::getCalendarItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 106
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 49
nc 1
nop 4
dl 0
loc 106
rs 9.1127
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
	require_once(BASE_PATH . 'server/includes/mapi/class.recurrence.php');
3
4
	/**
5
	 * Appointment Module
6
	 */
7
	class AppointmentListModule extends ListModule
8
	{
9
		/**
10
		 * @var date start interval of view visible
11
		 */
12
		private $startdate;
13
14
		/**
15
		 * @var date end interval of view visible
16
		 */
17
		private $enddate;
18
19
		/**
20
		 * Constructor
21
		 * @param int $id unique id.
22
		 * @param array $data list of all actions.
23
		 */
24
		function __construct($id, $data)
25
		{
26
			parent::__construct($id, $data);
27
28
			$this->properties = $GLOBALS["properties"]->getAppointmentListProperties();
29
30
			$this->startdate = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type date of property $startdate.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
31
			$this->enddate = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type date of property $enddate.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
32
		}
33
34
		/**
35
		 * Creates the notifiers for this module,
36
		 * and register them to the Bus.
37
		 */
38
		function createNotifiers()
39
		{
40
			$entryid = $this->getEntryID();
41
			$GLOBALS["bus"]->registerNotifier('appointmentlistnotifier', $entryid);
42
		}
43
44
		/**
45
		 * Executes all the actions in the $data variable.
46
		 * @return boolean true on success of false on fialure.
47
		 */
48
		function execute()
49
		{
50
			foreach($this->data as $actionType => $action)
51
			{
52
				if(isset($actionType)) {
53
					try {
54
						$store = $this->getActionStore($action);
55
						$entryid = $this->getActionEntryID($action);
56
57
						switch($actionType)
58
						{
59
							case "list":
60
								$this->messageList($store, $entryid, $action, $actionType);
0 ignored issues
show
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid of AppointmentListModule::messageList(). ( Ignorable by Annotation )

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

60
								$this->messageList($store, /** @scrutinizer ignore-type */ $entryid, $action, $actionType);
Loading history...
61
								break;
62
							case "search":
63
								// @FIXME add functionality to handle private items
64
								$this->search($store, $entryid, $action, $actionType);
65
								break;
66
							case "updatesearch":
67
								$this->updatesearch($store, $entryid, $action);
68
								break;
69
							case "stopsearch":
70
								$this->stopSearch($store, $entryid, $action);
71
								break;
72
							default:
73
								$this->handleUnknownActionType($actionType);
74
						}
75
					} catch (MAPIException $e) {
76
						if (isset($action['suppress_exception']) && $action['suppress_exception'] === true) {
77
							$e->setNotificationType('console');
0 ignored issues
show
Bug introduced by
The method setNotificationType() does not exist on MAPIException. ( Ignorable by Annotation )

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

77
							$e->/** @scrutinizer ignore-call */ 
78
           setNotificationType('console');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
78
						}
79
						$this->processException($e, $actionType);
80
					}
81
				}
82
			}
83
		}
84
85
		/**
86
		 * Function which retrieves a list of calendar items in a calendar folder
87
		 * @param object $store MAPI Message Store Object
88
		 * @param string $entryid entryid of the folder
89
		 * @param array $action the action data, sent by the client
90
		 * @param string $actionType the action type, sent by the client
91
		 * @return boolean true on success or false on failure
92
		 */
93
		function messageList($store, $entryid, $action, $actionType)
94
		{
95
			if($store && $entryid) {
96
				// initialize start and due date with false value so it will not take values from previous request
97
				$this->startdate = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type date of property $startdate.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
98
				$this->enddate = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type date of property $enddate.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
99
100
				if(isset($action["restriction"])) {
101
					if(isset($action["restriction"]["startdate"])) {
102
						$this->startdate = $action["restriction"]["startdate"];
103
					}
104
105
					if(isset($action["restriction"]["duedate"])) {
106
						$this->enddate = $action["restriction"]["duedate"];
107
					}
108
				}
109
110
				if($this->startdate && $this->enddate) {
111
					$data = array();
112
113
					if(is_array($entryid) && !empty($entryid)) {
0 ignored issues
show
introduced by
The condition is_array($entryid) is always false.
Loading history...
114
						$data["item"] = array();
115
						for($index = 0, $index2 = count($entryid); $index < $index2; $index++) {
116
							$this->getDelegateFolderInfo($store[$index]);
117
118
							// Set the active store in properties class and get the props based on active store.
119
							// we need to do this because of multi server env where shared store belongs to the different server.
120
							// Here name space is different per server. e.g. There is user A and user B and both are belongs to
121
							// different server and user B is shared store of user A because of that user A has 'categories' => -2062020578
122
							// and user B 'categories' => -2062610402,
123
							$GLOBALS["properties"]->setActiveStore($store[$index]);
124
							$this->properties = $GLOBALS["properties"]->getAppointmentListProperties();
125
126
							$data["item"] = array_merge($data["item"], $this->getCalendarItems($store[$index], $entryid[$index], $this->startdate, $this->enddate));
127
						}
128
					} else {
129
						$this->getDelegateFolderInfo($store);
130
						$data["item"] = $this->getCalendarItems($store, $entryid, $this->startdate, $this->enddate);
131
					}
132
133
					$this->addActionData("list", $data);
134
					$GLOBALS["bus"]->addData($this->getResponseData());
135
				} else {
136
					// for list view in calendar as startdate and enddate is passed as false
137
					// this will set sorting and paging for items in listview.
138
139
					$this->getDelegateFolderInfo($store);
140
141
142
					/* This is an override for parent::messageList(), which ignores an array of entryids / stores.
143
					*	 The following block considers this possibly and merges the data of several folders / stores.
144
					*/
145
146
					$this->searchFolderList = false; // Set to indicate this is not the search result, but a normal folder content
147
148
					if($store && $entryid) {
149
						// Restriction
150
						$this->parseRestriction($action);
0 ignored issues
show
Bug introduced by
$action of type array is incompatible with the type object expected by parameter $action of ListModule::parseRestriction(). ( Ignorable by Annotation )

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

150
						$this->parseRestriction(/** @scrutinizer ignore-type */ $action);
Loading history...
151
152
						// Sort
153
						$this->parseSortOrder($action, null, true);
154
155
						$limit = false;
156
						if(isset($action['restriction']['limit'])){
157
							$limit = $action['restriction']['limit'];
158
						} else {
159
							$limit = $GLOBALS['settings']->get('zarafa/v1/main/page_size', 50);
160
						}
161
162
						$isSearchFolder = isset($action['search_folder_entryid']);
163
						$entryid = $isSearchFolder ? hex2bin($action['search_folder_entryid']) : $entryid;
164
165
						if(!is_array($entryid) && !is_array($store)) {
0 ignored issues
show
introduced by
The condition is_array($entryid) is always false.
Loading history...
introduced by
The condition is_array($store) is always false.
Loading history...
166
							$entryid = [ $entryid ];
167
							$store = [ $store ];
168
						}
169
170
						// Get the table and merge the arrays
171
						$data = array();
172
						$items = array();
173
						for($i = 0, $c = count($entryid); $i < $c; $i++) {
174
							$newItems = $GLOBALS["operations"]->getTable($store[$i], $entryid[$i], $this->properties, $this->sort, $this->start, $limit, $this->restriction);
175
							$items = array_merge($items, $newItems['item']);
176
						}
177
178
						// If the request come from search folder then no need to send folder information
179
						if (!$isSearchFolder) {
180
							$contentCount = 0;
181
							$contentUnread = 0;
182
183
							// For each folder
184
							for($i = 0, $c = count($entryid); $i < $c; $i++) {
185
								//Open folder
186
								$folder = mapi_msgstore_openentry($store[$i], $entryid[$i]);
187
								// Obtain some statistics from the folder contents
188
								$content = mapi_getprops($folder, array(PR_CONTENT_COUNT, PR_CONTENT_UNREAD));
189
								if (isset($content[PR_CONTENT_COUNT])) {
190
									$contentCount += $content[PR_CONTENT_COUNT];
191
								}
192
193
								if (isset($content[PR_CONTENT_UNREAD])) {
194
									$contentUnread += $content[PR_CONTENT_UNREAD];
195
								}
196
							}
197
198
							$data["folder"] = array();
199
							$data["folder"]["content_count"] = $contentCount;
200
							$data["folder"]["content_unread"] = $contentUnread;
201
						}
202
203
						$items = $this->filterPrivateItems($items);
204
						// unset will remove the value but will not regenerate array keys, so we need to
205
						// do it here
206
						$data["item"] = $items;
207
208
						for($i = 0, $c = count($entryid); $i < $c; $i++) {
209
							// Allowing to hook in just before the data sent away to be sent to the client
210
							$GLOBALS['PluginManager']->triggerHook('server.module.listmodule.list.after', array(
211
								'moduleObject' =>& $this,
212
								'store' => $store[$i],
213
								'entryid' => $entryid[$i],
214
								'action' => $action,
215
								'data' =>& $data
216
							));
217
						}
218
219
						$this->addActionData($actionType, $data);
220
						$GLOBALS["bus"]->addData($this->getResponseData());
221
					}
222
				}
223
			}
224
		}
225
226
		/**
227
		 * Function to return all Calendar items in a given timeframe. This
228
		 * function also takes recurring items into account.
229
		 * @param object $store message store
230
		 * @param object $calendar folder
231
		 * @param date $start startdate of the interval
232
		 * @param date $end enddate of the interval
233
		 */
234
		function getCalendarItems($store, $entryid, $start, $end)
235
		{
236
			// Create mapping for restriction used properties which should not be send to the client.
237
			$properties = Array(
238
				"clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235",
239
				"clipend" => "PT_SYSTIME:PSETID_Appointment:0x8236",
240
			);
241
			$properties = getPropIdsFromStrings($store, $properties);
242
243
			$restriction =
244
				// OR
245
				//  - Either we want all appointments which fall within the given range
246
				//	- Or we want all recurring items which we manually check if an occurrence
247
				//	  exists which will fall inside the range
248
				Array(RES_OR,
249
					Array(
250
						// OR
251
						//	- Either we want all properties which fall inside the range (or overlap the range somewhere)
252
						//		(start < appointmentEnd && due > appointmentStart)
253
						//	- Or we want all zero-minute appointments which fall at the start of the restriction.
254
						// Note that this will effectively exclude any appointments which have an enddate on the restriction
255
						// start date, as those are not useful for us. Secondly, we exclude all zero-minute appointments
256
						// which fall on the end of the restriction as the restriction is <start, end].
257
						array(RES_OR,
258
							array(
259
								// AND
260
								//	- The AppointmentEnd must fall after the start of the range
261
								//	- The AppointmentStart must fall before the end of the range
262
								Array(RES_AND,
263
									Array(
264
										// start < appointmentEnd
265
										Array(RES_PROPERTY,
266
											Array(RELOP => RELOP_GT,
267
												ULPROPTAG => $this->properties["duedate"],
268
												VALUE => $start
269
											)
270
										),
271
										// due > appointmentStart
272
										Array(RES_PROPERTY,
273
											Array(RELOP => RELOP_LT,
274
												ULPROPTAG => $this->properties["startdate"],
275
												VALUE => $end
276
											)
277
										)
278
									)
279
								),
280
								// AND
281
								//	- The AppointmentStart equals the start of the range
282
								//	- The AppointmentEnd equals the start of the range
283
								// In other words the zero-minute appointments on the start of the range
284
								array(RES_AND,
285
									array(
286
										// appointmentStart == start
287
										array(RES_PROPERTY,
288
											Array(RELOP => RELOP_EQ,
289
												ULPROPTAG => $this->properties["startdate"],
290
												VALUE => $start
291
											)
292
										),
293
										// appointmentEnd == start
294
										array(RES_PROPERTY,
295
											Array(RELOP => RELOP_EQ,
296
												ULPROPTAG => $this->properties["duedate"],
297
												VALUE => $start
298
											)
299
										)
300
									)
301
								),
302
							)
303
						),
304
						//OR
305
						//(item[isRecurring] == true)
306
						Array(RES_AND,
307
							array(
308
						Array(RES_PROPERTY,
309
							Array(RELOP => RELOP_EQ,
310
								ULPROPTAG => $this->properties["recurring"],
311
								VALUE => true
312
							),
313
								),
314
								array(RES_AND,
315
									array(
316
										array(RES_PROPERTY,
317
											Array(RELOP => RELOP_GT,
318
												ULPROPTAG => $properties["clipend"],
319
												VALUE => $start
320
											)
321
										),
322
										array(RES_PROPERTY,
323
											Array(RELOP => RELOP_LT,
324
												ULPROPTAG => $properties["clipstart"],
325
												VALUE => $end
326
											)
327
										)
328
									)
329
								),
330
							)
331
						)
332
					)
333
			);// global OR
334
335
			$folder = mapi_msgstore_openentry($store, $entryid);
336
			$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS);
337
			$calendaritems = mapi_table_queryallrows($table, $this->properties, $restriction);
338
339
			return $this->processItems($calendaritems, $store, $entryid, $start, $end);
340
		}
341
342
		/**
343
		 * Process calendar items to prepare them for being sent back to the client
344
		 * @param array $calendaritems array of appointments retrieved from the mapi tablwe
345
 		 * @param object $store message store
346
		 * @param object $calendar folder
347
		 * @param date $start startdate of the interval
348
		 * @param date $end enddate of the interval
349
		 * @return array $items processed items
350
		 */
351
		function processItems($calendaritems, $store, $entryid, $start, $end)
352
		{
353
			$items = Array();
354
			$openedMessages = Array();
355
			$proptags = $GLOBALS["properties"]->getRecurrenceProperties();
356
357
			foreach($calendaritems as $calendaritem)
358
			{
359
				$item = null;
360
				if (isset($calendaritem[$this->properties["recurring"]]) && $calendaritem[$this->properties["recurring"]]) {
361
					$recurrence = new Recurrence($store, $calendaritem, $proptags);
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type resource expected by parameter $store of Recurrence::__construct(). ( Ignorable by Annotation )

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

361
					$recurrence = new Recurrence(/** @scrutinizer ignore-type */ $store, $calendaritem, $proptags);
Loading history...
362
					$recuritems = $recurrence->getItems($start, $end);
363
364
					foreach($recuritems as $recuritem)
365
					{
366
						$item = Conversion::mapMAPI2XML($this->properties, $recuritem);
367
368
						// Single occurrences are never recurring
369
						$item['props']['recurring'] = false;
370
371
						if(isset($recuritem["exception"])) {
372
							$item["props"]["exception"] = true;
373
						}
374
375
						if(isset($recuritem["basedate"])) {
376
							$item["props"]["basedate"] = $recuritem["basedate"];
377
						}
378
379
						if ( isset($recuritem["exception"]) ){
380
							// Add categories if they are set on the exception
381
							// We will create a new Recurrence object with the opened message,
382
							// so we can open the attachments. The attachments for this exception
383
							// contains the categories property (if changed)
384
							$msgEntryid = bin2hex($calendaritem[$this->properties["entryid"]]);
385
							if ( !isset($openedMessages[$msgEntryid]) ){
386
								// Open the message and add it to the openedMessages property
387
								$message = mapi_msgstore_openentry($store, $calendaritem[$this->properties["entryid"]]);
388
								$openedMessages[$msgEntryid] = $message;
389
							} else {
390
								// This message was already opened
391
								$message = $openedMessages[$msgEntryid];
392
							}
393
							// Now create a Recurrence object with the mapi message (instead of the message props)
394
							// so we can open the attachments
395
							$recurrence = new Recurrence($store, $message, $proptags);
396
							$exceptionatt = $recurrence->getExceptionAttachment($recuritem["basedate"]);
397
							if($exceptionatt) {
398
								// Existing exception (open existing item, which includes basedate)
399
								$exception = mapi_attach_openobj($exceptionatt, 0);
400
								$exceptionProps = $GLOBALS['operations']->getMessageProps($store, $exception, array('categories'=>$this->properties["categories"]));
401
402
								if ( isset($exceptionProps['props']['categories']) ){
403
									$item["props"]["categories"] = $exceptionProps['props']['categories'];
404
								}
405
							}
406
						}
407
408
						$item = $this->processPrivateItem($item);
409
410
						// only add it in response if its not removed by above function
411
						if(!empty($item)) {
412
							if (empty($item["props"]["commonstart"]) && isset($item["props"]["startdate"])) {
413
								$item["props"]["commonstart"] = $item["props"]["startdate"];
414
							}
415
							if (empty($item["props"]["commonend"]) && isset($item["props"]["duedate"])) {
416
								$item["props"]["commonend"] = $item["props"]["duedate"];
417
							}
418
							array_push($items, $item);
419
						}
420
					}
421
				} else {
422
					$item = Conversion::mapMAPI2XML($this->properties, $calendaritem);
423
424
					$item = $this->processPrivateItem($item);
425
426
					// only add it in response if its not removed by above function
427
					if(!empty($item)) {
428
						if (empty($item["props"]["commonstart"]) && isset($item["props"]["startdate"])) {
429
							$item["props"]["commonstart"] = $item["props"]["startdate"];
430
						}
431
						if (empty($item["props"]["commonend"]) && isset($item["props"]["duedate"])) {
432
							$item["props"]["commonend"] = $item["props"]["duedate"];
433
						}
434
						array_push($items,$item);
435
					}
436
				}
437
			}
438
439
			usort($items, array("AppointmentListModule", "compareCalendarItems"));
440
441
			return $items;
442
		}
443
444
		/**
445
		 * Function will be used to process private items in a list response, modules can
446
		 * can decide what to do with the private items, remove the entire row or just
447
		 * hide the data. This function will only hide the data of the private appointments.
448
		 * @param {Object} $item item properties
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
449
		 * @return {Object} item properties after processing private items
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
450
		 */
451
		function processPrivateItem($item)
452
		{
453
			if($this->startdate && $this->enddate) {
454
				if($this->checkPrivateItem($item)) {
455
					$item['props']['subject'] = _('Private Appointment');
456
					$item['props']['location'] = '';
457
					$item['props']['reminder'] = 0;
458
					$item['props']['access'] = 0;
459
					$item['props']['sent_representing_name'] = '';
460
					$item['props']['sender_name'] = '';
461
462
					return $item;
463
				}
464
465
				return $item;
466
			} else {
467
				// if we are in list view then we need to follow normal procedure of other listviews
468
				return parent::processPrivateItem($item);
469
			}
470
		}
471
472
		/**
473
		 * Function will sort items for the month view
474
		 * small startdate on top.
475
		 */
476
		public static function compareCalendarItems($a, $b)
477
		{
478
			$start_a = $a["props"]["startdate"];
479
			$start_b = $b["props"]["startdate"];
480
481
		   if ($start_a == $start_b) {
482
		       return 0;
483
		   }
484
		   return ($start_a < $start_b) ? -1 : 1;
485
		}
486
	}
487
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
488