Issues (752)

modules/class.suggestemailaddressmodule.php (3 issues)

1
<?php
2
3
/**
4
 * suggestEmailAddressModule.
5
 *
6
 * Class is used to store/retrieve suggestion list entries from a mapi property PR_EC_RECIPIENT_HISTORY_JSON
7
 * on default store. The format of recipient history that is stored in this property is shown below
8
 * {
9
 * 	 recipients : [
10
 *		'display_name' : 'foo bar',
11
 *		'smtp_address' : '[email protected]',
12
 *		'count' : 1,
13
 *		'last_used' : 1232313121,
14
 *		'object_type' : 6 // MAPI_MAILUSER
15
 *	 ]
16
 * }
17
 */
18
class suggestEmailAddressModule extends Module {
19
	public function __construct($id, $data) {
20
		parent::__construct($id, $data);
21
	}
22
23
	#[Override]
24
	public function execute() {
25
		try {
26
			// Retrieve the recipient history
27
			$storeProps = mapi_getprops($GLOBALS["mapisession"]->getDefaultMessageStore(), [PR_EC_RECIPIENT_HISTORY_JSON]);
28
			$recipient_history = false;
29
30
			if (isset($storeProps[PR_EC_RECIPIENT_HISTORY_JSON]) || propIsError(PR_EC_RECIPIENT_HISTORY_JSON, $storeProps) == MAPI_E_NOT_ENOUGH_MEMORY) {
31
				$datastring = streamProperty($GLOBALS["mapisession"]->getDefaultMessageStore(), PR_EC_RECIPIENT_HISTORY_JSON);
32
33
				if ($datastring !== "") {
34
					$recipient_history = json_decode_data($datastring, true);
35
				}
36
			}
37
38
			foreach ($this->data as $actionType => $action) {
39
				if (isset($actionType)) {
40
					switch ($actionType) {
41
						case 'delete':
42
							$this->deleteRecipient($action, $recipient_history);
0 ignored issues
show
$recipient_history of type false|object is incompatible with the type array expected by parameter $recipient_history of suggestEmailAddressModule::deleteRecipient(). ( Ignorable by Annotation )

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

42
							$this->deleteRecipient($action, /** @scrutinizer ignore-type */ $recipient_history);
Loading history...
43
							break;
44
45
						case 'list':
46
							$data = $this->getRecipientList($action, $recipient_history);
0 ignored issues
show
$recipient_history of type false|object is incompatible with the type array expected by parameter $recipient_history of suggestEmailAddressModule::getRecipientList(). ( Ignorable by Annotation )

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

46
							$data = $this->getRecipientList($action, /** @scrutinizer ignore-type */ $recipient_history);
Loading history...
47
48
							// Pass data on to be returned to the client
49
							$this->addActionData("list", $data);
50
							$GLOBALS["bus"]->addData($this->getResponseData());
51
52
							break;
53
					}
54
				}
55
			}
56
		}
57
		catch (MAPIException $e) {
58
			$this->processException($e, $actionType);
59
		}
60
	}
61
62
	public static function cmpSortResultList($a, $b) {
63
		if ($a['count'] < $b['count']) {
64
			return 1;
65
		}
66
		if ($a['count'] > $b['count']) {
67
			return -1;
68
		}
69
		$l_iReturnVal = strnatcasecmp((string) $a['display_name'], (string) $b['display_name']);
70
		if ($l_iReturnVal == 0) {
71
			$l_iReturnVal = strnatcasecmp((string) $a['smtp_address'], (string) $b['smtp_address']);
72
		}
73
74
		return $l_iReturnVal;
75
	}
76
77
	/**
78
	 * Function is used to delete a recipient entry from already stored recipient history
79
	 * in mapi property. it searches for deleteRecipients key in the action array which will
80
	 * contain email addresses of recipients that should be deleted in semicolon separated format.
81
	 *
82
	 * @param array $action            action data in associative array format
83
	 * @param array $recipient_history recipient history stored in mapi property
84
	 */
85
	public function deleteRecipient($action, $recipient_history) {
86
		if (isset($action) && !empty($recipient_history) && !empty($recipient_history['recipients'])) {
87
			/*
88
			 * A foreach is used instead of a normal for-loop to
89
			 * prevent the loop from finishing before the end of
90
			 * the array, because of the unsetting of elements
91
			 * in that array.
92
			 */
93
			foreach ($recipient_history['recipients'] as $index => $recipient) {
94
				if ($action['email_address'] == $recipient['email_address'] || $action['smtp_address'] == $recipient['smtp_address']) {
95
					unset($recipient_history['recipients'][$index]);
96
				}
97
			}
98
			// Re-indexing recipients' array to adjust index of deleted recipients
99
			$recipient_history['recipients'] = array_values($recipient_history['recipients']);
100
101
			// Write new recipient history to property
102
			$l_sNewRecipientHistoryJSON = json_encode($recipient_history);
103
104
			$stream = mapi_openproperty($GLOBALS["mapisession"]->getDefaultMessageStore(), PR_EC_RECIPIENT_HISTORY_JSON, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
105
			mapi_stream_setsize($stream, strlen($l_sNewRecipientHistoryJSON));
106
			mapi_stream_write($stream, $l_sNewRecipientHistoryJSON);
107
			mapi_stream_commit($stream);
108
			mapi_savechanges($GLOBALS["mapisession"]->getDefaultMessageStore());
109
		}
110
111
		// send success message to client
112
		$this->sendFeedback(true);
113
	}
114
115
	/**
116
	 * Function is used to get recipient history from mapi property based
117
	 * on the query specified by the client in action array.
118
	 *
119
	 * @param array $action            action data in associative array format
120
	 * @param array $recipient_history recipient history stored in mapi property
121
	 *
122
	 * @returns {Array} data holding recipients that matched the query.
123
	 */
124
	public function getRecipientList($action, $recipient_history) {
125
		if (!empty($action["query"]) && !empty($recipient_history) && !empty($recipient_history['recipients'])) {
126
			// Setup result array with match levels
127
			$l_aResult = [
128
				0 => [],
129
				1 => [],
130
			];
131
132
			// Loop through all the recipients
133
134
			for ($i = 0, $len = count($recipient_history['recipients']); $i < $len; ++$i) {
135
				// Prepare strings for case sensitive search
136
				$l_sName = strtolower((string) $recipient_history['recipients'][$i]['display_name']);
137
				$l_sEmail = strtolower((string) $recipient_history['recipients'][$i]['smtp_address']);
138
				$l_sSearchString = strtolower((string) $action["query"]);
139
140
				// Check for the presence of the search string
141
				$l_ibPosName = strpos($l_sName, $l_sSearchString);
142
				$l_ibPosEmail = strpos($l_sEmail, $l_sSearchString);
143
144
				// Check if the string is present in name or email fields
145
				if ($l_ibPosName !== false || $l_ibPosEmail !== false) {
146
					// Check if the found string matches from the start of the word
147
					if ($l_ibPosName === 0 || substr($l_sName, $l_ibPosName - 1, 1) == ' ' || $l_ibPosEmail === 0 || substr($l_sEmail, $l_ibPosEmail - 1, 1) == ' ') {
148
						array_push($l_aResult[0], [
149
							'display_name' => $recipient_history['recipients'][$i]['display_name'],
150
							'smtp_address' => $recipient_history['recipients'][$i]['smtp_address'],
151
							'email_address' => $recipient_history['recipients'][$i]['email_address'],
152
							'address_type' => $recipient_history['recipients'][$i]['address_type'],
153
							'count' => $recipient_history['recipients'][$i]['count'],
154
							'last_used' => $recipient_history['recipients'][$i]['last_used'],
155
							'object_type' => $recipient_history['recipients'][$i]['object_type'],
156
						]);
157
					// Does not match from start of a word, but start in the middle
158
					}
159
					else {
160
						array_push($l_aResult[1], [
161
							'display_name' => $recipient_history['recipients'][$i]['display_name'],
162
							'smtp_address' => $recipient_history['recipients'][$i]['smtp_address'],
163
							'email_address' => $recipient_history['recipients'][$i]['email_address'],
164
							'address_type' => $recipient_history['recipients'][$i]['address_type'],
165
							'count' => $recipient_history['recipients'][$i]['count'],
166
							'last_used' => $recipient_history['recipients'][$i]['last_used'],
167
							'object_type' => $recipient_history['recipients'][$i]['object_type'],
168
						]);
169
					}
170
				}
171
			}
172
173
			// Prevent the displaying of the exact match of the whole email address when only one item is found.
174
			if (count($l_aResult[0]) == 1 && empty($l_aResult[1]) && $l_sSearchString == strtolower((string) $l_aResult[0][0]['smtp_address'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $l_sSearchString does not seem to be defined for all execution paths leading up to this point.
Loading history...
175
				$recipientList = [];
176
			}
177
			else {
178
				/**
179
				 * Sort lists.
180
				 *
181
				 * This block of code sorts the two lists and creates one final list.
182
				 * The first list holds the matches based on whole words or words
183
				 * beginning with the search string and the second list contains the
184
				 * partial matches that start in the middle of the words.
185
				 * The first list is sorted on count (the number of emails sent to this
186
				 * email address), name and finally on the email address. This is done
187
				 * by a natural sort. When this first list already contains the maximum
188
				 * number of returned items the second list needs no sorting. If it has
189
				 * less, then the second list is sorted and included in the first list
190
				 * as well. At the end the final list is sorted on name and email again.
191
				 */
192
				$l_iMaxNumListItems = 10;
193
				$l_aSortedList = [];
194
				usort($l_aResult[0], [self::class, 'cmpSortResultList']);
195
				for ($i = 0, $len = min($l_iMaxNumListItems, count($l_aResult[0])); $i < $len; ++$i) {
196
					$l_aSortedList[] = $l_aResult[0][$i];
197
				}
198
				if (count($l_aSortedList) < $l_iMaxNumListItems) {
199
					$l_iMaxNumRemainingListItems = $l_iMaxNumListItems - count($l_aSortedList);
200
					usort($l_aResult[1], [self::class, 'cmpSortResultList']);
201
					for ($i = 0, $len = min($l_iMaxNumRemainingListItems, count($l_aResult[1])); $i < $len; ++$i) {
202
						$l_aSortedList[] = $l_aResult[1][$i];
203
					}
204
				}
205
206
				$recipientList = [];
207
				foreach ($l_aSortedList as $index => $recipient) {
208
					$recipient['id'] = count($recipientList) + 1;
209
					$recipientList[] = $recipient;
210
				}
211
			}
212
213
			$data = [
214
				'query' => $action["query"],
215
				'results' => $recipientList,
216
			];
217
		}
218
		else {
219
			$data = [
220
				'query' => $action["query"],
221
				'results' => [],
222
			];
223
		}
224
225
		return $data;
226
	}
227
}
228