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
Bug
introduced
by
![]() |
|||||
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
![]() |
|||||
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
|
|||||
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 |