|
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); |
|
|
|
|
|
|
43
|
|
|
break; |
|
44
|
|
|
|
|
45
|
|
|
case 'list': |
|
46
|
|
|
$data = $this->getRecipientList($action, $recipient_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'])) { |
|
|
|
|
|
|
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
|
|
|
|