Issues (752)

server/includes/modules/class.rulesmodule.php (4 issues)

1
<?php
2
3
/**
4
 * Rules Module
5
 * Module will be used to save rules information to rules table.
6
 */
7
class RulesModule extends Module {
8
	/**
9
	 * @var MAPITable contains resource of rules modify table
0 ignored issues
show
The type MAPITable 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...
10
	 */
11
	private $rulesFolder;
12
13
	/**
14
	 * Constructor.
15
	 *
16
	 * @param int   $id   unique id
17
	 * @param array $data list of all actions
18
	 */
19
	public function __construct($id, $data) {
20
		$this->properties = $GLOBALS['properties']->getRulesProperties();
21
		$this->rulesFolder = null;
22
23
		parent::__construct($id, $data);
24
	}
25
26
	/**
27
	 * Executes all the actions in the $data variable.
28
	 */
29
	#[Override]
30
	public function execute() {
31
		foreach ($this->data as $actionType => $action) {
32
			// Determine if the request contains multiple items or not. We couldn't add the storeEntryId to
33
			// the action data if it contained items because it was an array, so the storeEntryId
34
			// was added to all the items. We will pick it from the first item.
35
			if (isset($action[0])) {
36
				$storeEntryid = $action[0]['message_action']['store_entryid'];
37
			}
38
			else {
39
				$storeEntryid = $action['store_entryid'];
40
			}
41
42
			$ownStoreEntryId = $GLOBALS['mapisession']->getDefaultMessageStoreEntryId();
43
44
			try {
45
				if (ENABLE_SHARED_RULES !== true && !$GLOBALS['entryid']->compareEntryIds($storeEntryid, $ownStoreEntryId)) {
46
					// When the admin does not allow a user to set rules on the store of other users, but somehow
47
					// the user still tries this (probably hacking) we will not allow this
48
					throw new MAPIException(_('Setting mail filters on the stores of other users is not allowed.'));
49
				}
50
				$store = $GLOBALS['mapisession']->openMessageStore(hex2bin((string) $storeEntryid));
51
52
				switch ($actionType) {
53
					case 'list':
54
						$rules = $this->getRules($store);
55
						if ($rules) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rules of type array<string,array|array...d,array<string,array>>> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
56
							$this->addActionData('list', $rules);
57
							$GLOBALS['bus']->addData($this->getResponseData());
58
						}
59
						else {
60
							$this->sendFeedback(false);
61
						}
62
						break;
63
64
					case 'save':
65
						// When saving the rules, we expect _all_ rules
66
						// to have been send. So our first task, is to
67
						// delete all existing rules.
68
						$this->deleteRules($store);
69
70
						// Now can save all rules, note that $action can contain just a store key when
71
						// all rules are removed.
72
						if (count($action) > 1) {
73
							$this->saveRules($store, $action);
74
						}
75
76
						// delete (outlook) client rules
77
						$this->deleteOLClientRules($store);
78
79
						// Respond with the full set of rules.
80
						$rules = $this->getRules($store);
81
						if ($rules) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rules of type array<string,array|array...d,array<string,array>>> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
82
							$this->addActionData('update', $rules);
83
							$GLOBALS['bus']->addData($this->getResponseData());
84
						}
85
						else {
86
							$this->sendFeedback(false);
87
						}
88
						break;
89
90
					default:
91
						$this->handleUnknownActionType($actionType);
92
				}
93
			}
94
			catch (MAPIException $e) {
95
				$this->processException($e, $actionType);
96
			}
97
		}
98
	}
99
100
	public function getRulesFolder($store = false) {
101
		if (!$this->rulesFolder) {
102
			if ($store === false) {
103
				$store = $GLOBALS['mapisession']->getDefaultMessageStore();
104
			}
105
			$this->rulesFolder = mapi_msgstore_getreceivefolder($store);
106
		}
107
108
		return $this->rulesFolder;
109
	}
110
111
	/**
112
	 * Create a restriction to search for rules which have a rule provider
113
	 * which starts with RuleOrganizer. Outlook will generate some rules
114
	 * with PR_RULE_PROVIDER RuleOrganizer2 for client-only rules, however
115
	 * we still want to show these in the client, hence we perform a prefix
116
	 * search.
117
	 *
118
	 * @return array The restriction which should be applied to the RulesTable
119
	 *               to obtain all the rules which should be shown to the user
120
	 */
121
	public function getRestriction() {
122
		return [RES_CONTENT,
123
			[
124
				FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE,
125
				ULPROPTAG => PR_RULE_PROVIDER,
126
				VALUE => [
127
					PR_RULE_PROVIDER => 'RuleOrganizer',
128
				],
129
			],
130
		];
131
	}
132
133
	/**
134
	 * Get all rules of a store.
135
	 *
136
	 * This function opens the rules table for the specified store, and reads
137
	 * all rules with PR_RULE_PROVIDER equal to 'RuleOrganizer'. These are the rules
138
	 * that the user sees when managing rules from Outlook.
139
	 *
140
	 * @param resource $store Store in which rules reside
141
	 *
142
	 * @return array rules data
143
	 */
144
	public function getRules($store) {
145
		$rules_folder = $this->getRulesFolder($store);
146
		$rulesTable = mapi_folder_getrulestable($rules_folder);
147
148
		mapi_table_restrict($rulesTable, $this->getRestriction(), TBL_BATCH);
149
		mapi_table_sort($rulesTable, [PR_RULE_SEQUENCE => TABLE_SORT_ASCEND], TBL_BATCH);
150
151
		$rows = mapi_table_queryallrows($rulesTable, $this->properties);
152
153
		$rules = [];
154
155
		foreach ($rows as &$row) {
156
			$rules[] = Conversion::mapMAPI2XML($this->properties, $row);
157
		}
158
		unset($row);
159
160
		return ['item' => $rules];
161
	}
162
163
	/**
164
	 * Function used to delete all the rules the user currently has.
165
	 *
166
	 * @param resource $store in which we want to delete the rules
167
	 */
168
	public function deleteRules($store) {
169
		$rules_folder = $this->getRulesFolder($store);
170
		$rulesTable = mapi_folder_getrulestable($rules_folder);
171
		mapi_table_restrict($rulesTable, $this->getRestriction(), TBL_BATCH);
172
		mapi_table_sort($rulesTable, [PR_RULE_SEQUENCE => TABLE_SORT_ASCEND], TBL_BATCH);
173
		$rows = mapi_table_queryallrows($rulesTable, $this->properties);
174
175
		$rules = [];
176
177
		foreach ($rows as &$row) {
178
			$rules[] = [
179
				'rowflags' => ROW_REMOVE,
180
				'properties' => $row,
181
			];
182
		}
183
184
		if (!empty($rules)) {
185
			mapi_folder_modifyrules($rules_folder, $rules);
186
		}
187
	}
188
189
	/**
190
	 * Function will be used to create/update rule in user's rules table.
191
	 * This function only usee ROW_MODIFY flag to save rules data, Which is correct when modifying existing rules
192
	 * but for adding new rules Gromox automatically checks existence of rule id and if it si not then
193
	 * use ROW_ADD flag.
194
	 *
195
	 * @param resource $store     The store into which the rules must be saved
196
	 * @param array    $rulesData rules data that should be deleted
197
	 */
198
	public function saveRules($store, $rulesData) {
199
		if (is_assoc_array($rulesData)) {
200
			// wrap single rule in an array
201
			$rulesData = [$rulesData];
202
		}
203
204
		// save rules in rules table
205
		$saveRules = [];
206
		for ($index = 0, $len = count($rulesData); $index < $len; ++$index) {
207
			$rule = $rulesData[$index];
208
			if (!empty($rule['props'])) {
209
				$rule += $rule['props'];
210
			}
211
212
			$rule = Conversion::mapXML2MAPI($this->properties, $rule);
213
214
			// Always reset the PR_RULE_ID property, it is going
215
			// to be regenerated by the server anyway, so we can safely
216
			// discard whatever value the client has given.
217
			$rule[PR_RULE_ID] = $index;
218
219
			// provide default action and rule if client has not provided
220
			if (empty($rule[PR_RULE_ACTIONS])) {
221
				$rule[PR_RULE_ACTIONS] = [
222
					[
223
						'action' => OP_DEFER_ACTION,
224
						'dam' => hex2bin('E0C810000120000100000000000000010000000000000001000000360000000200FFFF00000C004352756C65456C656D656E7490010000010000000000000001000000018064000000010000000000000001000000'),
225
					],
226
				];
227
			}
228
229
			if (empty($rule[PR_RULE_CONDITION])) {
230
				$rule[PR_RULE_CONDITION] = [
231
					RES_EXIST,
232
					[
233
						ULPROPTAG => PR_MESSAGE_CLASS,
234
					],
235
				];
236
			}
237
238
			if (empty($rule[PR_RULE_NAME])) {
239
				$rule[PR_RULE_NAME] = _('Untitled rule');
240
			}
241
242
			// generate rule provider data
243
			$rule[PR_RULE_PROVIDER_DATA] = pack('VVa*', 1, $rule[PR_RULE_ID], Conversion::UnixTimeToCOleDateTime(time()));
244
245
			$saveRules[] = [
246
				'rowflags' => ROW_ADD,
247
				'properties' => $rule,
248
			];
249
		}
250
251
		if (!empty($saveRules)) {
252
			mapi_folder_modifyrules($this->getRulesFolder($store), $saveRules);
253
		}
254
	}
255
256
	/**
257
	 * Function will delete (outlook) client rules. Outlook maintains client rules
258
	 * in associated table of inbox, When we create/delete/update rule from webapp
259
	 * it won't match with outlook's client rules, so it will confuse outlook and
260
	 * it will ask user to preserve whether client or server side rules, so every time
261
	 * we save rules we need to remove this outlook generated client rule to remove
262
	 * ambigiuty.
263
	 *
264
	 * @param resource $store (optional) current user's store
265
	 */
266
	public function deleteOLClientRules($store = false) {
267
		if ($store === false) {
268
			$store = $GLOBALS['mapisession']->getDefaultMessageStore();
269
		}
270
271
		$inbox = mapi_msgstore_getreceivefolder($store);
272
273
		// get inbox' associatedTable
274
		$associatedTable = mapi_folder_getcontentstable($inbox, MAPI_ASSOCIATED);
275
276
		mapi_table_restrict(
277
			$associatedTable,
278
			[RES_CONTENT,
279
				[
280
					FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
281
					ULPROPTAG => PR_MESSAGE_CLASS,
282
					VALUE => [
283
						PR_MESSAGE_CLASS => "IPM.RuleOrganizer",
284
					],
285
				],
286
			]
287
		);
288
		$messages = mapi_table_queryallrows($associatedTable, [PR_ENTRYID]);
289
290
		$deleteMessages = [];
291
		for ($i = 0, $len = count($messages); $i < $len; ++$i) {
292
			array_push($deleteMessages, $messages[$i][PR_ENTRYID]);
293
		}
294
295
		if (!empty($deleteMessages)) {
296
			mapi_folder_deletemessages($inbox, $deleteMessages);
297
		}
298
	}
299
300
	/**
301
	 * Function does customization of MAPIException based on module data.
302
	 * like, here it will generate display message based on actionType
303
	 * for particular exception.
304
	 *
305
	 * @param object     $e             exception object
306
	 * @param string     $actionType    the action type, sent by the client
307
	 * @param MAPIobject $store         store object of the message
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...
308
	 * @param string     $parententryid parent entryid of the message
309
	 * @param string     $entryid       entryid of the message/folder
310
	 * @param array      $action        the action data, sent by the client
311
	 */
312
	#[Override]
313
	public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
314
		if (is_null($e->displayMessage)) {
315
			switch ($actionType) {
316
				case 'list':
317
					$e->setDisplayMessage(_('Could not load rules.'));
318
					break;
319
320
				case 'save':
321
					$e->setDisplayMessage(_('Could not save rules.'));
322
					break;
323
			}
324
		}
325
	}
326
}
327