Issues (752)

server/includes/modules/class.delegatesmodule.php (16 issues)

1
<?php
2
3
/**
4
 * DelegatesModule
5
 * Class can be used to get delegate information of a particular user.
6
 * The delegate information can be created/update using this class as well.
7
 */
8
class DelegatesModule extends Module {
9
	/**
10
	 * @var array contains entryid's of all default folders
11
	 */
12
	private $defaultFolders;
13
14
	/**
15
	 * @var array contains delegate properties from special message (LocalFreebusy)
16
	 */
17
	private $delegateProps;
18
19
	/**
20
	 * @var resource of LocalFreeBusy Message. This contains for delegates.
21
	 */
22
	private $localFreeBusyMessage;
23
24
	/**
25
	 * @var resource of FreeBusy Folder in IPM_SUBTREE. This permissions for freebusy folder used in calendar.
26
	 */
27
	private $freeBusyFolder;
28
29
	/**
30
	 * @var resource of default store of the current user
31
	 */
32
	private $defaultStore;
33
34
	/**
35
	 * Constructor.
36
	 *
37
	 * @param int   $id   unique id
38
	 * @param array $data list of all actions
39
	 */
40
	public function __construct($id, $data) {
41
		$this->defaultFolders = [];
42
		$this->delegateProps = [];
43
		$this->localFreeBusyMessage = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type resource of property $localFreeBusyMessage.

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

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

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...
46
47
		parent::__construct($id, $data);
48
	}
49
50
	/**
51
	 * Executes all the actions in the $data variable.
52
	 */
53
	#[Override]
54
	public function execute() {
55
		foreach ($this->data as $actionType => $action) {
56
			if (isset($actionType)) {
57
				try {
58
					match ($actionType) {
59
						'list' => $this->delegateList(),
0 ignored issues
show
Are you sure the usage of $this->delegateList() targeting DelegatesModule::delegateList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
60
						'open' => $this->openDelegate($action),
0 ignored issues
show
Are you sure the usage of $this->openDelegate($action) targeting DelegatesModule::openDelegate() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
61
						'save' => $this->saveDelegates($action),
0 ignored issues
show
Are you sure the usage of $this->saveDelegates($action) targeting DelegatesModule::saveDelegates() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
62
						'delete' => $this->deleteDelegates($action),
0 ignored issues
show
Are you sure the usage of $this->deleteDelegates($action) targeting DelegatesModule::deleteDelegates() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
63
						default => $this->handleUnknownActionType($actionType),
0 ignored issues
show
Are you sure the usage of $this->handleUnknownActionType($actionType) targeting Module::handleUnknownActionType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
64
					};
65
				}
66
				catch (MAPIException $e) {
67
					$this->processException($e, $actionType);
68
				}
69
			}
70
		}
71
	}
72
73
	/* Generic functions to access data */
74
75
	/**
76
	 * Function will return values of the properties PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS and
77
	 * PR_SCHDINFO_DELEGATE_NAMES from LocalFreeBusyMessage of the user's store.
78
	 *
79
	 * @param resource $localFreeBusyMessage (optional) local freebusy message of the user's store
80
	 *
81
	 * @return array values of delegate properties
82
	 */
83
	public function getDelegateProps($localFreeBusyMessage = false) {
84
		if (empty($this->delegateProps)) {
85
			if ($localFreeBusyMessage === false) {
86
				$localFreeBusyMessage = FreeBusy::getLocalFreeBusyMessage($this->getDefaultStore());
87
			}
88
89
			$this->delegateProps = mapi_getprops($localFreeBusyMessage, [PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_SCHDINFO_DELEGATE_NAMES]);
90
91
			// check if properties exists or not, if not then initialize with default values
92
			// this way in caller functions we don't need to check for non-existent properties
93
			if (!isset($this->delegateProps[PR_DELEGATE_FLAGS])) {
94
				$this->delegateProps[PR_DELEGATE_FLAGS] = [];
95
			}
96
97
			if (!isset($this->delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS])) {
98
				$this->delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS] = [];
99
			}
100
101
			if (!isset($this->delegateProps[PR_SCHDINFO_DELEGATE_NAMES])) {
102
				$this->delegateProps[PR_SCHDINFO_DELEGATE_NAMES] = [];
103
			}
104
		}
105
106
		return $this->delegateProps;
107
	}
108
109
	/**
110
	 * Function will return array of entryids of default folders of the user's store.
111
	 *
112
	 * @param resource $store (optional) user's store
113
	 *
114
	 * @return array default folder entryids
115
	 */
116
	public function getDefaultFolders($store = false) {
117
		if (empty($this->defaultFolders)) {
118
			if ($store === false) {
119
				$store = $this->getDefaultStore();
120
			}
121
122
			// Get root store
123
			$root = mapi_msgstore_openentry($store);
124
125
			// Get Inbox folder
126
			$inbox = mapi_msgstore_getreceivefolder($store);
127
			$inboxprops = mapi_getprops($inbox, [PR_ENTRYID]);
128
129
			// Get entryids of default folders.
130
			$rootStoreProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID]);
131
132
			$this->defaultFolders = [
133
				'calendar' => $rootStoreProps[PR_IPM_APPOINTMENT_ENTRYID],
134
				'tasks' => $rootStoreProps[PR_IPM_TASK_ENTRYID],
135
				'inbox' => $inboxprops[PR_ENTRYID],
136
				'contacts' => $rootStoreProps[PR_IPM_CONTACT_ENTRYID],
137
				'notes' => $rootStoreProps[PR_IPM_NOTE_ENTRYID],
138
				'journal' => $rootStoreProps[PR_IPM_JOURNAL_ENTRYID],
139
			];
140
		}
141
142
		return $this->defaultFolders;
143
	}
144
145
	/**
146
	 * Function will return index of a particular delegate, this index can be used to get information of
147
	 * delegate from PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS and
148
	 * PR_SCHDINFO_DELEGATE_NAMES properties.
149
	 *
150
	 * @param string $entryId entryid of delegate
151
	 *
152
	 * @return int index of the delegate information
153
	 */
154
	public function getDelegateIndex($entryId) {
155
		$delegateProps = $this->getDelegateProps();
156
157
		// Check if user is existing delegate.
158
		if (!empty($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS])) {
159
			return array_search($entryId, $delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_search($ent...NFO_DELEGATE_ENTRYIDS]) also could return the type string which is incompatible with the documented return type integer.
Loading history...
160
		}
161
162
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
163
	}
164
165
	/**
166
	 * Function will return resource of the default store of the current logged in user.
167
	 *
168
	 * @return resource current user's store
169
	 */
170
	public function getDefaultStore() {
171
		if (!$this->defaultStore) {
172
			$this->defaultStore = $GLOBALS['mapisession']->getDefaultMessageStore();
173
		}
174
175
		return $this->defaultStore;
176
	}
177
178
	/**
179
	 * Function will return properties of the user from addressbook based on passed user's entryid.
180
	 *
181
	 * @param string $userEntryId entryid of the user from addressbook
182
	 *
183
	 * @return array properties of user from addressbook
184
	 */
185
	public function getUserInfo($userEntryId) {
186
		// default return stuff
187
		$result = [
188
			'display_name' => _('Unknown user/group'),
189
			'entryid' => null,
190
		];
191
192
		// open the addressbook
193
		$ab = $GLOBALS['mapisession']->getAddressbook();
194
195
		try {
196
			// try userid as normal user
197
			$user = mapi_ab_openentry($ab, $userEntryId);
198
		}
199
		catch (MAPIException $e) {
200
			if ($e->getCode() === MAPI_E_NOT_FOUND) {
201
				$e->setHandled();
202
203
				return $result;
204
			}
205
		}
206
207
		$props = mapi_getprops($user, [PR_DISPLAY_NAME]);
208
		$result['display_name'] = $props[PR_DISPLAY_NAME];
209
		$result['entryid'] = bin2hex($userEntryId);
210
211
		return $result;
212
	}
213
214
	/* Functions to get delegates information */
215
216
	/**
217
	 * Function will return all the delegates information for current user.
218
	 */
219
	public function delegateList() {
220
		$delegateProps = $this->getDelegateProps();
221
222
		$data = [];
223
224
		// get delegate meeting rule
225
		$delegateMeetingRule = $this->getDelegateMeetingRule();
226
227
		// Get permissions of all delegates.
228
		if (!empty($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS])) {
229
			for ($i = 0, $len = count($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]); $i < $len; ++$i) {
230
				array_push($data, $this->getDelegatePermissions($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS][$i], $delegateMeetingRule));
231
			}
232
		}
233
234
		$this->addActionData('list', ['item' => $data]);
235
		$GLOBALS['bus']->addData($this->getResponseData());
236
	}
237
238
	/**
239
	 * Function will return the permissions assigned for a particular delegate user.
240
	 *
241
	 * @param array $delegate the delegate information sent by client
242
	 */
243
	public function openDelegate($delegate) {
244
		// Get permissions of a delegate.
245
		$data = $this->getDelegatePermissions(hex2bin((string) $delegate['entryid']));
246
247
		$this->addActionData('item', ['item' => $data]);
248
		$GLOBALS['bus']->addData($this->getResponseData());
249
	}
250
251
	/**
252
	 * Function will return information of a particular delegate from current user's store.
253
	 *
254
	 * @param string $userEntryId         entryid of the delegate
255
	 * @param array  $delegateMeetingRule (optional) information of the delegate meeting rule that can be used to check if
256
	 *                                    current delegate exists in the meeting rule
257
	 *
258
	 * @return array delegate information
259
	 */
260
	public function getDelegatePermissions($userEntryId, $delegateMeetingRule = false) {
0 ignored issues
show
The parameter $delegateMeetingRule 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

260
	public function getDelegatePermissions($userEntryId, /** @scrutinizer ignore-unused */ $delegateMeetingRule = false) {

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...
261
		$delegateProps = $this->getDelegateProps();
262
		$delegateIndex = $this->getDelegateIndex($userEntryId);
263
		$userinfo = $this->getUserInfo($userEntryId);
264
265
		$delegate = [];
266
		$delegate['entryid'] = bin2hex($userEntryId);
267
268
		$delegate['props'] = [];
269
		$delegate['props']['display_name'] = $userinfo['display_name'];
270
		$delegate['props']['can_see_private'] = isset($delegateProps[PR_DELEGATE_FLAGS][$delegateIndex]) ? ($delegateProps[PR_DELEGATE_FLAGS][$delegateIndex] == 1) : false;
271
272
		$delegate['props'] = array_merge($delegate['props'], $this->getFolderPermissions($userEntryId));
273
274
		return $delegate;
275
	}
276
277
	/**
278
	 * Function will return folder permissions of a delegate user.
279
	 *
280
	 * @param string $userEntryId entryid of the delegate
281
	 *
282
	 * @return array folder permissions of a delegate user
283
	 */
284
	public function getFolderPermissions($userEntryId) {
285
		$delegateRights = [];
286
		$store = $this->getDefaultStore();
287
288
		foreach ($this->getDefaultFolders($store) as $folderName => $folderEntryId) {
289
			$folder = mapi_msgstore_openentry($store, $folderEntryId);
290
291
			// Get all users who has permissions
292
			$grants = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
293
294
			// Find current delegate and get permission.
295
			foreach ($grants as $id => $grant) {
296
				if ($GLOBALS["entryid"]->compareEntryIds(bin2hex($userEntryId), bin2hex((string) $grant['userid']))) {
297
					$delegateRights['rights_' . $folderName] = $grant['rights'];
298
				}
299
			}
300
		}
301
302
		return $delegateRights;
303
	}
304
305
	/**
306
	 * Function will return properties of meeting rule that is used to send meeting related messages to delegate.
307
	 *
308
	 * @return array delegate meeting rule information
309
	 */
310
	public function getDelegateMeetingRule() {
311
		$inbox = mapi_msgstore_getreceivefolder($this->getDefaultStore());
312
		$rulesTable = mapi_folder_getrulestable($inbox);
313
		// get delegate meeting rule
314
		$restriction = [RES_CONTENT,
315
			[
316
				FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
317
				ULPROPTAG => PR_RULE_PROVIDER,
318
				VALUE => [
319
					PR_RULE_PROVIDER => 'Schedule+ EMS Interface',
320
				],
321
			],
322
		];
323
		mapi_table_restrict($rulesTable, $restriction);
324
		// there will be only one rule, so fetch that only
325
		$delegateMeetingRule = mapi_table_queryrows($rulesTable, $GLOBALS['properties']->getRulesProperties(), 0, 1);
326
327
		return !empty($delegateMeetingRule) ? $delegateMeetingRule[0] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! empty($delegate...eMeetingRule[0] : false could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
328
	}
329
330
	/* Functions to update delegates information */
331
332
	/**
333
	 * Function which saves delegates information sent from client.
334
	 *
335
	 * @param array $delegates the delegates information sent by client
336
	 */
337
	public function saveDelegates($delegates) {
338
		$responseData = [];
339
340
		// @FIXME currently client only sends a single delegate in a request, if we can change that to contain
341
		// multiple delegates that would be good
342
343
		if (is_assoc_array($delegates)) {
344
			// wrap single delegate in an array
345
			$delegates = [$delegates];
346
		}
347
348
		for ($index = 0, $len = count($delegates); $index < $len; ++$index) {
349
			$delegate = $delegates[$index];
350
			$this->setFolderPermissions($delegate);
351
			// set properties for delegates on user's freebusy folder
352
			array_push($responseData, ['entryid' => $delegate['entryid']]);
353
		}
354
		$this->setDelegateProps($delegates);
355
		// set delegate meeting rule
356
		$this->setDelegateMeetingRule($delegates);
357
358
		// send response to indicate success
359
		if (!empty($responseData)) {
360
			$this->addActionData('update', ['item' => $responseData]);
361
			$GLOBALS['bus']->addData($this->getResponseData());
362
		}
363
	}
364
365
	/**
366
	 * Function will update PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS and
367
	 * PR_SCHDINFO_DELEGATE_NAMES properties in the current user's store.
368
	 *
369
	 * @param mixed $delegates
370
	 */
371
	public function setDelegateProps($delegates) {
372
		$localFreeBusyMessage = FreeBusy::getLocalFreeBusyMessage($this->getDefaultStore());
373
		$delegateProps = $this->getDelegateProps($localFreeBusyMessage);
374
		$len = count($delegates);
375
		for ($i = 0; $i < $len; ++$i) {
376
			$delegate = $delegates[$i];
377
			$len1 = count($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]);
378
			for ($j = 0; $j < $len1; ++$j) {
379
				if ($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS][$j] == hex2bin((string) $delegate['entryid'])) {
380
					break;
381
				}
382
			}
383
			$delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS][$j] = hex2bin((string) $delegate['entryid']);
384
			if (isset($delegate['props']['display_name'])) {
385
				$delegateProps[PR_SCHDINFO_DELEGATE_NAMES][$j] = $delegate['props']['display_name'];
386
			}
387
			else {
388
				$addrBook = $GLOBALS['mapisession']->getAddressbook();
389
				$user = mapi_ab_openentry($addrBook, hex2bin((string) $delegate['entryid']));
390
				if (empty($user)) {
391
					$delegateProps[PR_SCHDINFO_DELEGATE_NAMES][$j] = "";
392
				}
393
				else {
394
					$userProps = mapi_getprops($user, [PR_SMTP_ADDRESS]);
395
					if (empty($userProps[PR_SMTP_ADDRESS])) {
396
						$delegateProps[PR_SCHDINFO_DELEGATE_NAMES][$j] = "";
397
					}
398
					else {
399
						$delegateProps[PR_SCHDINFO_DELEGATE_NAMES][$j] = $userProps[PR_SMTP_ADDRESS];
400
					}
401
				}
402
			}
403
			if (isset($delegate['props']['can_see_private'])) {
404
				$delegateProps[PR_DELEGATE_FLAGS][$j] = $delegate['props']['can_see_private'];
405
			}
406
			else {
407
				$delegateProps[PR_DELEGATE_FLAGS][$j] = false;
408
			}
409
		}
410
		mapi_setprops($localFreeBusyMessage, $delegateProps);
411
		mapi_savechanges($localFreeBusyMessage);
412
		// unset the module variable, so subsequent calls will have the updated value
413
		unset($this->delegateProps);
414
	}
415
416
	/**
417
	 * Function will set folder permissions for a delegate user.
418
	 *
419
	 * @param array $delegate delegate information sent from client
420
	 */
421
	public function setFolderPermissions($delegate) {
422
		$store = $this->getDefaultStore();
423
424
		// Get all default folders and set permissions.
425
		foreach ($this->getDefaultFolders($store) as $folderName => $folderEntryID) {
426
			// we need to only modify those permissions which are modified on client
427
			if (isset($delegate['props']['rights_' . $folderName]) && $delegate['props']['rights_' . $folderName] !== '') {
428
				$folder = mapi_msgstore_openentry($store, $folderEntryID);
429
430
				// Set new permissions.
431
				$acls = [
432
					[
433
						'type' => ACCESS_TYPE_GRANT,
434
						'userid' => hex2bin((string) $delegate['entryid']),
435
						'rights' => $delegate['props']['rights_' . $folderName],
436
						'state' => RIGHT_NEW | RIGHT_AUTOUPDATE_DENIED,
437
						'memberid' => 0,
438
					],
439
				];
440
441
				mapi_zarafa_setpermissionrules($folder, $acls);
442
				mapi_savechanges($folder);
443
444
				if ($folderName === 'calendar') {
445
					$freeBusyFolder = FreeBusy::getLocalFreeBusyFolder($store);
446
447
					if ($freeBusyFolder !== false) {
448
						// set permissions on free/busy message
449
						$acls[0]['rights'] |= ecRightsDefaultPublic;
450
451
						mapi_zarafa_setpermissionrules($freeBusyFolder, $acls);
452
						mapi_savechanges($freeBusyFolder);
453
					}
454
				}
455
			}
456
		}
457
	}
458
459
	/**
460
	 * Function which creates/modifies delegate meeting rule in user store
461
	 * to send meeting request mails to delegates also.
462
	 *
463
	 * @param array $delegates all delegate information
464
	 */
465
	public function setDelegateMeetingRule($delegates) {
466
		$delegateMeetingRule = $this->getDelegateMeetingRule();
467
		if ($delegateMeetingRule !== false) {
0 ignored issues
show
The condition $delegateMeetingRule !== false is always true.
Loading history...
468
			$users = $delegateMeetingRule[PR_RULE_ACTIONS][0]['adrlist'];
469
		}
470
		else {
471
			$users = [];
472
		}
473
		// open addressbook to get information of all users
474
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
475
		$len = count($delegates);
476
		for ($i = 0; $i < $len; ++$i) {
477
			$delegate = $delegates[$i];
478
			// get user info, using entryid
479
			$user = mapi_ab_openentry($addrBook, hex2bin((string) $delegate['entryid']));
480
			$userProps = mapi_getprops($user, [PR_ENTRYID, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_SMTP_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE, PR_DISPLAY_TYPE_EX]);
481
482
			if (is_array($userProps)) {
483
				// add recipient type prop, to specify type of recipient in mail
484
				$userProps[PR_RECIPIENT_TYPE] = MAPI_TO;
485
				$len1 = count($users);
486
				for ($j = 0; $j < $len1; ++$j) {
487
					if ($userProps[PR_ENTRYID] == $users[$j][PR_ENTRYID]) {
488
						break;
489
					}
490
				}
491
				$users[$j] = $userProps;
492
			}
493
		}
494
		// only continue if any delegate has set the flag
495
		if (!empty($users)) {
496
			if ($delegateMeetingRule === false) {
0 ignored issues
show
The condition $delegateMeetingRule === false is always false.
Loading history...
497
				$this->createDelegateMeetingRule($users);
498
			}
499
			else {
500
				$this->modifyDelegateMeetingRule($delegateMeetingRule, $users);
501
			}
502
		}
503
	}
504
505
	/**
506
	 * Function will create a new delegate meeting rule if it is not present in current user's store.
507
	 *
508
	 * @param array $usersInfo user properties which should be added in PR_RULE_ACTIONS
509
	 */
510
	public function createDelegateMeetingRule($usersInfo) {
511
		// create new rule
512
		$rule = [];
513
514
		// no need to pass rule_id when creating new rule
515
		$rule[PR_RULE_ACTIONS] = [
516
			[
517
				'action' => OP_DELEGATE,
518
				// don't set this value it will have no effect, its hardcoded to FWD_PRESERVE_SENDER | FWD_DO_NOT_MUNGE_MSG
519
				'flavor' => 0,
520
				'flags' => 0,
521
				'adrlist' => $usersInfo,
522
			],
523
		];
524
525
		$rule[PR_RULE_CONDITION] = [RES_AND,
526
			[
527
				[RES_CONTENT,
528
					[
529
						FUZZYLEVEL => FL_PREFIX,
530
						ULPROPTAG => PR_MESSAGE_CLASS,
531
						VALUE => [PR_MESSAGE_CLASS => 'IPM.Schedule.Meeting'],
532
					],
533
				],
534
				[RES_NOT,
535
					[
536
						[RES_EXIST,
537
							[
538
								ULPROPTAG => PR_DELEGATED_BY_RULE,
539
							],
540
						],
541
					],
542
				],
543
				[RES_OR,
544
					[
545
						[RES_NOT,
546
							[
547
								[RES_EXIST,
548
									[
549
										ULPROPTAG => PR_SENSITIVITY,
550
									],
551
								],
552
							],
553
						],
554
						[RES_PROPERTY,
555
							[
556
								RELOP => RELOP_NE,
557
								ULPROPTAG => PR_SENSITIVITY,
558
								VALUE => [PR_SENSITIVITY => SENSITIVITY_PRIVATE],
559
							],
560
						],
561
					],
562
				],
563
			],
564
		];
565
566
		$rule[PR_RULE_NAME] = '';
567
		$rule[PR_RULE_PROVIDER_DATA] = '';		// 0 byte binary string
568
		$rule[PR_RULE_STATE] = ST_ENABLED;
569
		$rule[PR_RULE_LEVEL] = 0;
570
		$rule[PR_RULE_SEQUENCE] = 0;
571
		$rule[PR_RULE_PROVIDER] = 'Schedule+ EMS Interface';
572
		$rule[PR_RULE_USER_FLAGS] = 0;
573
574
		$rows = [
575
			0 => [
576
				'rowflags' => ROW_ADD,
577
				'properties' => $rule,
578
			],
579
		];
580
		$inbox = mapi_msgstore_getreceivefolder($this->getDefaultStore());
581
		mapi_folder_modifyrules($inbox, $rows);
582
	}
583
584
	public function modifyDelegateMeetingRule($delegateMeetingRule, $users) {
585
		$inbox = mapi_msgstore_getreceivefolder($this->getDefaultStore());
586
		if (count($users) > 0) {
587
			// update the adrlist in the rule
588
			$delegateMeetingRule[PR_RULE_ACTIONS][0]['adrlist'] = $users;
589
590
			$rows = [
591
				[
592
					'rowflags' => ROW_MODIFY,
593
					'properties' => $delegateMeetingRule,
594
				],
595
			];
596
		}
597
		else {
598
			// no users remaining in the rule so delete the rule
599
			$rows = [
600
				0 => [
601
					'rowflags' => ROW_REMOVE,
602
					'properties' => $delegateMeetingRule,
603
				],
604
			];
605
		}
606
		mapi_folder_modifyrules($inbox, $rows);
607
	}
608
609
	/* Functions to delete delegates information */
610
611
	/**
612
	 * Function which deletes delegates information sent by client.
613
	 *
614
	 * @param array $delegates delegates information sent by client
615
	 */
616
	public function deleteDelegates($delegates) {
617
		if (is_assoc_array($delegates)) {
618
			// wrap single delegate in an array
619
			$delegates = [$delegates];
620
		}
621
		foreach ($delegates as $delegate) {
622
			// set properties for delegates on user's freebusy folder
623
			$this->deleteDelegateProps($delegate);
624
625
			// set permissions on user's default folders
626
			$this->deleteFolderPermissions($delegate);
627
		}
628
		// delete delegate meeting rule
629
		$this->removeDelegatesFromDelegateMeetingRule($delegates);
630
		$this->sendFeedback(true);
631
	}
632
633
	/**
634
	 * Function will delete values from PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS and PR_SCHDINFO_DELEGATE_NAMES and save the properties back if its not empty.
635
	 *
636
	 * @param array $delegate delegate information sent from client
637
	 */
638
	public function deleteDelegateProps($delegate) {
639
		$localFreeBusyMessage = FreeBusy::getLocalFreeBusyMessage($this->getDefaultStore());
640
		$delegateProps = $this->getDelegateProps($localFreeBusyMessage);
641
		$delegateIndex = -1;
642
		$len = count($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]);
643
		for ($i = 0; $i < $len; ++$i) {
644
			if ($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS][$i] == hex2bin((string) $delegate["entryid"])) {
645
				$delegateIndex = $i;
646
				break;
647
			}
648
		}
649
		if ($delegateIndex == -1) {
650
			return;
651
		}
652
		unset($delegateProps[PR_DELEGATE_FLAGS][$delegateIndex], $delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS][$delegateIndex], $delegateProps[PR_SCHDINFO_DELEGATE_NAMES][$delegateIndex]);
653
654
		// unset will remove the value but will not regenerate array keys, so we need to
655
		// do it here
656
		$delegateProps[PR_DELEGATE_FLAGS] = array_values($delegateProps[PR_DELEGATE_FLAGS]);
657
		$delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS] = array_values($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]);
658
		$delegateProps[PR_SCHDINFO_DELEGATE_NAMES] = array_values($delegateProps[PR_SCHDINFO_DELEGATE_NAMES]);
659
660
		if (!empty($delegateProps[PR_SCHDINFO_DELEGATE_ENTRYIDS])) {
661
			mapi_setprops($localFreeBusyMessage, $delegateProps);
662
		}
663
		else {
664
			// Delete delegate properties.
665
			mapi_deleteprops($localFreeBusyMessage, [PR_DELEGATE_FLAGS, PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_SCHDINFO_DELEGATE_NAMES]);
666
		}
667
668
		mapi_savechanges($localFreeBusyMessage);
669
670
		// unset the module variable, so subsequent calls will have the updated value
671
		unset($this->delegateProps);
672
	}
673
674
	/**
675
	 * Function which deletes permissions from all default folder for a particular delegate.
676
	 *
677
	 * @param array $delegate delegate's information sent by client
678
	 */
679
	public function deleteFolderPermissions($delegate) {
680
		$store = $this->getDefaultStore();
681
682
		// Get all default folders and set permissions.
683
		foreach ($this->getDefaultFolders($store) as $folderName => $folderEntryID) {
684
			$folder = mapi_msgstore_openentry($store, $folderEntryID);
685
686
			// delete current acl's
687
			$acls = [
688
				[
689
					'type' => ACCESS_TYPE_GRANT,
690
					'userid' => hex2bin((string) $delegate['entryid']),
691
					'rights' => ecRightsNone,
692
					'state' => RIGHT_DELETED | RIGHT_AUTOUPDATE_DENIED,
693
					'memberid' => 0,
694
				],
695
			];
696
697
			mapi_zarafa_setpermissionrules($folder, $acls);
698
699
			if ($folderName === 'calendar') {
700
				$freeBusyFolder = FreeBusy::getLocalFreeBusyFolder($store);
701
702
				if ($freeBusyFolder !== false) {
703
					mapi_zarafa_setpermissionrules($freeBusyFolder, $acls);
704
					mapi_savechanges($freeBusyFolder);
705
				}
706
			}
707
708
			mapi_savechanges($folder);
709
		}
710
	}
711
712
	/**
713
	 * Function will remove delegates from delegate meeting rule when the user is deleted from delegate list.
714
	 *
715
	 * @param array $delegates all delegate information that are deleted
716
	 */
717
	public function removeDelegatesFromDelegateMeetingRule($delegates) {
718
		$delegateMeetingRule = $this->getDelegateMeetingRule();
719
		if ($delegateMeetingRule === false) {
0 ignored issues
show
The condition $delegateMeetingRule === false is always false.
Loading history...
720
			// no delegate rule exists, nothing to do
721
			return;
722
		}
723
		$len = count($delegates);
724
		$old_users = $delegateMeetingRule[PR_RULE_ACTIONS][0]['adrlist'];
725
		$new_users = [];
726
		foreach ($old_users as $user) {
727
			for ($index = 0; $index < $len; ++$index) {
728
				if ($user[PR_ENTRYID] == hex2bin((string) $delegates[$index]['entryid'])) {
729
					break;
730
				}
731
			}
732
			if ($index == $len) {
733
				$new_users[] = $user;
734
			}
735
		}
736
		$this->modifyDelegateMeetingRule($delegateMeetingRule, $new_users);
737
	}
738
739
	/* Functions for exception handling */
740
741
	/**
742
	 * Function does customization of MAPIException based on module data.
743
	 * like, here it will generate display message based on actionType
744
	 * for particular exception.
745
	 *
746
	 * @param object     $e             Exception object
747
	 * @param string     $actionType    the action type, sent by the client
748
	 * @param MAPIobject $store         store object of the current user
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...
749
	 * @param string     $parententryid parent entryid of the message
750
	 * @param string     $entryid       entryid of the message/folder
751
	 * @param array      $action        the action data, sent by the client
752
	 */
753
	#[Override]
754
	public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
755
		switch ($actionType) {
756
			case 'save':
757
				$e->setDisplayMessage(_('Could not save delegate information.'));
758
				break;
759
760
			case 'delete':
761
				$e->setDisplayMessage(_('Could not delete delegate.'));
762
				break;
763
764
			case 'list':
765
				$e->setDisplayMessage(_('Can not get list of delegates.'));
766
				break;
767
		}
768
769
		parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
770
	}
771
}
772