grommunio /
grommunio-web
| 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
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
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) { |
||||
|
0 ignored issues
–
show
The type
MAPIException 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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||||
| 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 |