Issues (752)

server/includes/modules/class.module.php (9 issues)

1
<?php
2
3
require_once __DIR__ . '/../exceptions/class.SearchException.php';
4
5
/**
6
 * Module
7
 * Superclass of every module. Many default functions are defined in this class.
8
 */
9
class Module {
10
	/**
11
	 * @var string entryid, which will be registered by the bus object
12
	 */
13
	public $entryid;
14
15
	/**
16
	 * @var array list of the results, which is send to the client
17
	 */
18
	public $responseData;
19
20
	/**
21
	 * @var array list of all the errors occurred
22
	 */
23
	public $errors;
24
25
	/**
26
	 * @var State The state object which refers to the statefile
27
	 */
28
	public $sessionState;
29
30
	/**
31
	 * @var array data stored in session for this module
32
	 */
33
	public $sessionData;
34
35
	/**
36
	 * @var array list of the properties necessary for the module
37
	 */
38
	public $properties;
39
40
	/**
41
	 * @var list of the folder list properties
42
	 */
43
	public $list_properties;
44
45
	/**
46
	 * Constructor.
47
	 *
48
	 * @param int   $id   unique id
49
	 * @param array $data list of all actions
50
	 */
51
	public function __construct(public $id, public $data) {
52
		$this->errors = [];
53
		$this->responseData = [];
54
		$this->sessionState = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type State of property $sessionState.

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...
55
		$this->sessionData = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $sessionData.

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...
56
57
		$this->createNotifiers();
58
59
		// Get the store from $data and set it to properties class.
60
		// It is requires for multi server environment where namespace differs.
61
		// e.g. 'categories' => -2062020578, 'categories' => -2062610402,
62
		if (isset($GLOBALS['properties'])) {
63
			$GLOBALS['properties']->setStore($this->getActionStore($this->getActionData($this->data)));
64
		}
65
	}
66
67
	/**
68
	 * Creates the notifiers for this module,
69
	 * and register them to the Bus.
70
	 */
71
	public function createNotifiers() {}
72
73
	/**
74
	 * Executes all the actions in the $data variable.
75
	 */
76
	public function execute() {
77
		// you must implement this function for each module
78
	}
79
80
	/**
81
	 * This will call $handleException of updating the MAPIException based in the module data.
82
	 * When this is done, $sendFeedback will be called to send the message to the client.
83
	 *
84
	 * @param object     $e             exception object
85
	 * @param string     $actionType    the action type, sent by the client
86
	 * @param MAPIobject $store         store object of the store
0 ignored issues
show
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...
87
	 * @param string     $parententryid parent entryid of the message
88
	 * @param string     $entryid       entryid of the message/folder
89
	 * @param array      $action        the action data, sent by the client
90
	 */
91
	public function processException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
92
		$this->handleException($e, $actionType, $store, $parententryid, $entryid, $action);
93
		$this->sendFeedback(false, $this->errorDetailsFromException($e));
94
	}
95
96
	/**
97
	 * Function does customization of MAPIException based on module data.
98
	 * like, here it will generate display message based on actionType
99
	 * for particular exception.
100
	 *
101
	 * @param object     $e             exception object
102
	 * @param string     $actionType    the action type, sent by the client
103
	 * @param MAPIobject $store         store object of the message
104
	 * @param string     $parententryid parent entryid of the message
105
	 * @param string     $entryid       entryid of the message/folder
106
	 * @param array      $action        the action data, sent by the client
107
	 */
108
	public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
109
		if (is_null($e->displayMessage)) {
110
			switch ($actionType) {
111
				case "save":
112
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
113
						$e->setDisplayMessage(_("You have insufficient privileges to save this message."));
114
					}
115
					else {
116
						$e->setDisplayMessage(_("Could not save message."));
117
					}
118
					$e->allowToShowDetailMessage = true;
119
					break;
120
121
				case "delete":
122
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
123
						$e->setDisplayMessage(_("You have insufficient privileges to delete this message."));
124
					}
125
					else {
126
						$e->setDisplayMessage(_("Could not delete message."));
127
					}
128
					break;
129
130
				case "cancelMeetingRequest":
131
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
132
						$e->setDisplayMessage(_("You have insufficient privileges to cancel this Meeting Request."));
133
					}
134
					else {
135
						$e->setDisplayMessage(_("Could not cancel Meeting Request."));
136
					}
137
					break;
138
139
				case "declineMeetingRequest":
140
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
141
						$e->setDisplayMessage(_("You have insufficient privileges to decline this Meeting Request."));
142
					}
143
					else {
144
						$e->setDisplayMessage(_("Could not decline Meeting Request."));
145
					}
146
					break;
147
148
				case "acceptMeetingRequest":
149
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
150
						$e->setDisplayMessage(_("You have insufficient privileges to accept this Meeting Request."));
151
					}
152
					else {
153
						$e->setDisplayMessage(_("Could not accept Meeting Request."));
154
					}
155
					break;
156
157
				case "cancelInvitation":
158
					if ($e->getCode() == MAPI_E_NO_ACCESS) {
159
						$e->setDisplayMessage(_("You have insufficient privileges to cancel Meeting Request invitation."));
160
					}
161
					else {
162
						$e->setDisplayMessage(_("Could not cancel Meeting Request invitations."));
163
					}
164
					break;
165
166
				case "updatesearch":
167
				case "stopsearch":
168
				case "search":
169
					if ($e->getCode() == MAPI_E_NOT_INITIALIZED) {
170
						$e->setDisplayMessage(_("You can not continue search operation on this folder."));
171
					}
172
					else {
173
						$e->setDisplayMessage(_("Error in search, please try again."));
174
					}
175
					break;
176
177
				case "expand":
178
					$e->setDisplayMessage(_("Error in distribution list expansion."));
179
					break;
180
			}
181
			Log::Write(
182
				LOGLEVEL_ERROR,
183
				"Module::handleException():" . $actionType . ": " . $e->displayMessage
184
			);
185
		}
186
	}
187
188
	/**
189
	 * Get quota information of user store and check for over qouta restrictions,
190
	 * if any qouta (softquota/hardquota) limit is exceeded then it will simply
191
	 * return appropriate message string according to quota type(hardquota/softquota).
192
	 *
193
	 * @param mixed $store Store object of the store
194
	 *
195
	 * @return string short message according to quota type(hardquota/softquota) or a blank string
196
	 */
197
	public function getOverQuotaMessage($store) {
198
		if ($store === false) {
199
			$store = $GLOBALS['mapisession']->getDefaultMessageStore();
200
		}
201
202
		$storeProps = mapi_getprops($store, [PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD, PR_QUOTA_RECEIVE_THRESHOLD, PR_MESSAGE_SIZE_EXTENDED]);
203
204
		$quotaDetails = [
205
			'store_size' => round($storeProps[PR_MESSAGE_SIZE_EXTENDED] / 1024),
206
			'quota_warning' => $storeProps[PR_QUOTA_WARNING_THRESHOLD],
207
			'quota_soft' => $storeProps[PR_QUOTA_SEND_THRESHOLD],
208
			'quota_hard' => $storeProps[PR_QUOTA_RECEIVE_THRESHOLD],
209
		];
210
211
		if ($quotaDetails['quota_hard'] !== 0 && $quotaDetails['store_size'] > $quotaDetails['quota_hard']) {
212
			return _('The message store has exceeded its hard quota limit.') . '<br/>' .
213
					_('To reduce the amount of data in this message store, select some items that you no longer need, delete them and cleanup your Deleted Items folder.');
214
		}
215
216
		// if hard quota limit doesn't restrict the operation then check for soft qouta limit
217
		if ($quotaDetails['quota_soft'] !== 0 && $quotaDetails['store_size'] > $quotaDetails['quota_soft']) {
218
			return _('The message store has exceeded its soft quota limit.') . '<br/> ' .
219
					_('To reduce the amount of data in this message store, select some items that you no longer need, delete them and cleanup your Deleted Items folder.');
220
		}
221
222
		return '';
223
	}
224
225
	/**
226
	 * sends a success or error message to client based on parameters passed.
227
	 *
228
	 * @param bool  $success              operation completed successfully or not
229
	 * @param array $data                 the data array that will be send to the client as a response to success/failure
230
	 * @param bool  $addResponseDataToBus if true then data will be added to bus otherwise data
231
	 *                                    will be stored in module and later requests can add it to bus
232
	 */
233
	public function sendFeedback($success = false, $data = [], $addResponseDataToBus = true) {
234
		// Send success/error message to client
235
		$this->addActionData($success === true ? "success" : "error", $data);
236
237
		if ($addResponseDataToBus) {
238
			$GLOBALS["bus"]->addData($this->getResponseData());
239
		}
240
	}
241
242
	/**
243
	 * Function will retrieve error details from exception object based on exception type.
244
	 * it should also send type of exception with the data. so client can know which type
245
	 * of exception is generated.
246
	 *
247
	 * @param object $exception the exception object which is generated
248
	 *
249
	 * @return array error data
250
	 */
251
	public function errorDetailsFromException($exception) {
252
		if (!$exception->isHandled) {
253
			if ($exception instanceof MAPIException) {
254
				$exception->setHandled();
255
256
				return [
257
					"type" => ERROR_MAPI,
258
					"info" => [
259
						"hresult" => $exception->getCode(),
260
						"title" => $exception->getTitle(),
261
						"hresult_name" => get_mapi_error_name($exception->getCode()),
262
						"file" => $exception->getFileLine(),
263
						"display_message" => $exception->getDisplayMessage(),
264
						"details_message" => $exception->getDetailsMessage(),
265
					],
266
				];
267
			}
268
			if ($exception instanceof ZarafaException) {
269
				$exception->setHandled();
270
271
				return [
272
					"type" => ERROR_ZARAFA,
273
					"info" => [
274
						"file" => $exception->getFileLine(),
275
						"title" => $exception->getTitle(),
276
						"display_message" => $exception->getDisplayMessage(),
277
						"original_message" => $exception->getMessage(),
278
					],
279
				];
280
			}
281
		}
282
283
		return [];
284
	}
285
286
	/**
287
	 * Function which returns an entryid, which is used to register this module. It
288
	 * searches in the class variable $data for a ParentEntryID or an EntryID.
289
	 *
290
	 * @return string an entryid if found, false if entryid not found
291
	 */
292
	public function getEntryID() {
293
		$entryid = false;
294
		foreach ($this->data as $action) {
295
			if (isset($action["parent_entryid"]) && !empty($action["parent_entryid"])) {
296
				$entryid = $action["parent_entryid"];
297
			}
298
			elseif (isset($action["entryid"]) && !empty($action["entryid"])) {
299
				$entryid = $action["entryid"];
300
			}
301
		}
302
303
		return $entryid;
304
	}
305
306
	/**
307
	 * Returns all the errors, which occurred.
308
	 *
309
	 * @return array an array of all the errors, which occurred
310
	 */
311
	public function getErrors() {
312
		return $this->errors;
313
	}
314
315
	/**
316
	 * Returns the response data.
317
	 *
318
	 * @return array An array of the response data. This data is send to the client.
319
	 */
320
	public function getData() {
321
		return $this->responseData;
322
	}
323
324
	/**
325
	 * Sets the action data, which will be executed.
326
	 *
327
	 * @param array $data array of all the actions
328
	 */
329
	public function setData($data) {
330
		$this->data = $data;
331
	}
332
333
	/**
334
	 * Function which returns MAPI Message Store Object. It
335
	 * searches in the variable $action for a storeid.
336
	 *
337
	 * @param array $action the XML data retrieved from the client
338
	 *
339
	 * @return object MAPI Message Store Object, false if storeid is not found in the $action variable
340
	 */
341
	public function getActionStore($action) {
342
		$store = false;
343
344
		try {
345
			if (isset($action["store_entryid"]) && !empty($action["store_entryid"])) {
346
				if (is_array($action["store_entryid"])) {
347
					$store = [];
348
					foreach ($action["store_entryid"] as $store_id) {
349
						array_push($store, $GLOBALS["mapisession"]->openMessageStore(hex2bin((string) $store_id)));
350
					}
351
				}
352
				elseif (ctype_xdigit((string) $action["store_entryid"])) {
353
					$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin((string) $action["store_entryid"]));
354
				}
355
			}
356
		}
357
		catch (Exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
358
		}
359
360
		return $store;
361
	}
362
363
	/**
364
	 * Function which returns a parent entryid. It
365
	 * searches in the variable $action for a parententryid.
366
	 *
367
	 * @param array $action the XML data retrieved from the client
368
	 *
369
	 * @return object MAPI Message Store Object, false if parententryid is not found in the $action variable
370
	 */
371
	public function getActionParentEntryID($action) {
372
		$parententryid = false;
373
374
		if (isset($action["parent_entryid"]) && !empty($action["parent_entryid"])) {
375
			$parententryid = hex2bin((string) $action["parent_entryid"]);
376
		}
377
378
		return $parententryid;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parententryid returns the type false|string which is incompatible with the documented return type object.
Loading history...
379
	}
380
381
	/**
382
	 * Function which returns an entryid. It
383
	 * searches in the variable $action for an entryid.
384
	 *
385
	 * @param array $action the XML data retrieved from the client
386
	 *
387
	 * @return mixed MAPI Message Store Object, false if entryid is not found in the $action variable
388
	 */
389
	public function getActionEntryID($action) {
390
		$entryid = false;
391
392
		if (isset($action["entryid"]) && !empty($action["entryid"])) {
393
			if (is_array($action["entryid"])) {
394
				$entryid = [];
395
				foreach ($action["entryid"] as $action_entryid) {
396
					array_push($entryid, hex2bin((string) $action_entryid));
397
				}
398
			}
399
			else {
400
				$entryid = hex2bin((string) $action["entryid"]);
401
			}
402
		}
403
404
		return $entryid;
405
	}
406
407
	/**
408
	 * Helper function which used to get the action data from request.
409
	 *
410
	 * @param array $data list of all actions
411
	 *
412
	 * @return array $action the json data retrieved from the client
413
	 */
414
	public function getActionData($data) {
415
		$actionData = false;
416
		foreach ($data as $actionType => $action) {
417
			if (isset($actionType)) {
418
				$actionData = $action;
419
			}
420
		}
421
422
		return $actionData;
423
	}
424
425
	/**
426
	 * Function which adds action data to module, so later it can be retrieved to send.
427
	 *
428
	 * @param string $actionType type of action that response data corresponds
429
	 * @param mixed  $data
430
	 */
431
	public function addActionData($actionType, $data) {
432
		if (!isset($this->responseData[$actionType])) {
433
			$this->responseData[$actionType] = $data;
434
		}
435
	}
436
437
	/**
438
	 * Function which returns response data that will be sent to client. If there isn't any data added
439
	 * to response data then it will return a blank array.
440
	 *
441
	 * @return object response data
442
	 */
443
	public function getResponseData() {
444
		if (!empty($this->responseData)) {
445
			return
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($this->getM...> $this->responseData)) returns the type array<string,array<integer,array>> which is incompatible with the documented return type object.
Loading history...
446
				[
447
					$this->getModuleName() => [
448
						$this->id => $this->responseData,
449
					],
450
				];
451
		}
452
453
		return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type object.
Loading history...
454
	}
455
456
	/**
457
	 * Function which returns name of the module class.
458
	 *
459
	 * @return string module name
460
	 */
461
	public function getModuleName() {
462
		return strtolower(static::class);
463
	}
464
465
	/**
466
	 * Function which will handle unknown action type for all modules.
467
	 *
468
	 * @param string $actionType action type
469
	 */
470
	public function handleUnknownActionType($actionType) {
471
		$this->sendFeedback(
472
			false,
473
			[
474
				"type" => ERROR_ZARAFA,
475
				"info" => [
476
					"display_message" => _("Could not process request data properly."),
477
					"original_message" => sprintf(_("Unknown action type specified - %s"), $actionType),
478
				],
479
			]
480
		);
481
		Log::Write(
482
			LOGLEVEL_ERROR,
483
			"Module::handleUnknownActionType(): ERROR_ZARAFA : " . _("Could not process request data properly.")
484
		);
485
	}
486
487
	/**
488
	 * Loads sessiondata of the module from state file on disk.
489
	 */
490
	public function loadSessionData() {
491
		$this->sessionState = new State('module_sessiondata');
492
		$this->sessionState->open();
493
		$this->sessionData = $this->sessionState->read($this->getModuleName());
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->sessionState->read($this->getModuleName()) can also be of type string. However, the property $sessionData is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
494
	}
495
496
	/**
497
	 * Saves sessiondata of the module to the state file on disk.
498
	 */
499
	public function saveSessionData() {
500
		if ($this->sessionData !== false) {
0 ignored issues
show
The condition $this->sessionData !== false is always true.
Loading history...
501
			$this->sessionState->write($this->getModuleName(), $this->sessionData);
502
		}
503
		$this->sessionState->close();
504
	}
505
}
506