grommunio /
grommunio-web
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * ResolveNames Module |
||
| 4 | */ |
||
| 5 | class ResolveNamesModule extends Module |
||
| 6 | { |
||
| 7 | /** |
||
| 8 | * Constructor |
||
| 9 | */ |
||
| 10 | function __construct($id, $data) |
||
| 11 | { |
||
| 12 | parent::__construct($id, $data); |
||
| 13 | } |
||
| 14 | |||
| 15 | /** |
||
| 16 | * Executes all the actions in the $data variable. |
||
| 17 | * @return boolean true on success of false on fialure. |
||
| 18 | */ |
||
| 19 | function execute() |
||
| 20 | { |
||
| 21 | foreach($this->data as $actionType => $action) |
||
| 22 | { |
||
| 23 | if(isset($actionType)) { |
||
| 24 | try { |
||
| 25 | switch($actionType) |
||
| 26 | { |
||
| 27 | case 'checknames': |
||
| 28 | $this->checkNames($action); |
||
| 29 | break; |
||
| 30 | default: |
||
| 31 | $this->handleUnknownActionType($actionType); |
||
| 32 | } |
||
| 33 | } catch (MAPIException $e) { |
||
| 34 | $this->processException($e, $actionType); |
||
| 35 | } |
||
| 36 | } |
||
| 37 | } |
||
| 38 | } |
||
| 39 | |||
| 40 | /** |
||
| 41 | * Function which checks the names, sent by the client. This function is used |
||
| 42 | * when a user wants to sent an email and want to check the names filled in |
||
| 43 | * by the user in the to, cc and bcc field. This function uses the global |
||
| 44 | * user list to check if the names are correct. |
||
| 45 | * @param array $action the action data, sent by the client |
||
| 46 | * @return boolean true on success or false on failure |
||
| 47 | */ |
||
| 48 | function checkNames($action) |
||
| 49 | { |
||
| 50 | if(isset($action['resolverequests'])) { |
||
| 51 | $data = array(); |
||
| 52 | $excludeLocalContacts = !empty($action['exclude_local_contacts']) ? $action['exclude_local_contacts'] : false; |
||
| 53 | $excludeGABGroups = !empty($action['exclude_gab_groups']) ? $action['exclude_gab_groups'] : false; |
||
| 54 | |||
| 55 | $resolveRequest = $action['resolverequests']; |
||
| 56 | if(!is_array($resolveRequest)) { |
||
| 57 | $resolveRequest = array($resolveRequest); |
||
| 58 | } |
||
| 59 | |||
| 60 | // open addressbook |
||
| 61 | // When local contacts need to be excluded we have pass true as the first argument |
||
| 62 | // so we will not have any Contact Providers set on the Addressbook resource. |
||
| 63 | $ab = $GLOBALS['mapisession']->getAddressbook($excludeLocalContacts); |
||
| 64 | |||
| 65 | $ab_entryid = mapi_ab_getdefaultdir($ab); |
||
| 66 | $ab_dir = mapi_ab_openentry($ab, $ab_entryid); |
||
| 67 | $resolveResponse = Array(); |
||
| 68 | |||
| 69 | // check names |
||
| 70 | foreach($resolveRequest as $query) { |
||
| 71 | if (is_array($query) && isset($query['id'])) { |
||
| 72 | $responseEntry = Array(); |
||
| 73 | $responseEntry['id'] = $query['id']; |
||
| 74 | |||
| 75 | if (!empty($query['display_name']) || !empty($query['email_address'])) { |
||
| 76 | $responseEntry['result'] = $this->searchAddressBook($ab, $ab_dir, $query, $excludeGABGroups); |
||
| 77 | $resolveResponse[] = $responseEntry; |
||
| 78 | } |
||
| 79 | } |
||
| 80 | } |
||
| 81 | |||
| 82 | $data['resolveresponse'] = $resolveResponse; |
||
| 83 | |||
| 84 | $this->addActionData('checknames', $data); |
||
| 85 | $GLOBALS['bus']->addData($this->getResponseData()); |
||
| 86 | } |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * This function searches the addressbook specified for users and returns an array with data |
||
| 91 | * Please note that the returning array must be UTF8 |
||
| 92 | * |
||
| 93 | * @param {MAPIAddressbook} $ab The addressbook |
||
| 94 | * @param {MAPIAbContainer} $ab_dir The addressbook container |
||
| 95 | * @param {String} $query The search query, case is ignored |
||
| 96 | * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving |
||
| 97 | */ |
||
| 98 | function searchAddressBook($ab, $ab_dir, $query, $excludeGABGroups) |
||
| 99 | { |
||
| 100 | // Prefer resolving the email_address. This allows the user |
||
| 101 | // to resolve recipients with a display name that matches a EX |
||
| 102 | // user with an alternative (external) email address. |
||
| 103 | $searchstr = empty($query['email_address']) ? $query['display_name'] : $query['email_address']; |
||
| 104 | // If the address_type is 'EX' then we are resolving something which must be found in |
||
| 105 | // the GAB as an exact match. So add the flag EMS_AB_ADDRESS_LOOKUP to ensure we will not |
||
| 106 | // get multiple results when multiple items have a partial match. |
||
| 107 | $flags = $query['address_type'] === 'EX' ? EMS_AB_ADDRESS_LOOKUP : 0; |
||
| 108 | |||
| 109 | try { |
||
| 110 | // First, try an addressbook lookup |
||
| 111 | $rows = mapi_ab_resolvename($ab, array ( array(PR_DISPLAY_NAME => $searchstr) ) , $flags); |
||
| 112 | } catch (MAPIException $e) { |
||
| 113 | if ($e->getCode() == MAPI_E_AMBIGUOUS_RECIP) { |
||
| 114 | // Ambiguous, show possibilities: |
||
| 115 | $table = mapi_folder_getcontentstable($ab_dir, MAPI_DEFERRED_ERRORS); |
||
| 116 | $restriction = $this->getAmbigiousContactRestriction($searchstr, $excludeGABGroups, PR_ACCOUNT); |
||
| 117 | |||
| 118 | mapi_table_restrict($table, $restriction, TBL_BATCH); |
||
| 119 | mapi_table_sort($table, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH); |
||
| 120 | |||
| 121 | $rows = mapi_table_queryallrows($table, array(PR_ACCOUNT, PR_ADDRTYPE, PR_DISPLAY_NAME, PR_ENTRYID, PR_SEARCH_KEY, PR_OBJECT_TYPE, PR_SMTP_ADDRESS, PR_DISPLAY_TYPE_EX, PR_EMAIL_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE)); |
||
| 122 | |||
| 123 | $rows = array_merge($rows, $this->getAmbigiousContactResolveResults($ab, $searchstr, $excludeGABGroups)); |
||
| 124 | } else if ($e->getCode() == MAPI_E_NOT_FOUND) { |
||
| 125 | $rows = array(); |
||
| 126 | // If we still can't find anything, and we were searching for a SMTP user |
||
| 127 | // we can generate a oneoff entry which contains the information of the user. |
||
| 128 | if ($query['address_type'] === 'SMTP' && !empty($query['email_address'])) { |
||
| 129 | $rows[] = array( |
||
| 130 | PR_ACCOUNT => $query['email_address'], PR_ADDRTYPE => 'SMTP', PR_EMAIL_ADDRESS => $query['email_address'], |
||
| 131 | PR_DISPLAY_NAME => $query['display_name'], PR_DISPLAY_TYPE_EX => DT_REMOTE_MAILUSER, PR_DISPLAY_TYPE => DT_MAILUSER, |
||
| 132 | PR_SMTP_ADDRESS => $query['email_address'], PR_OBJECT_TYPE => MAPI_MAILUSER, |
||
| 133 | PR_ENTRYID => mapi_createoneoff($query['display_name'], 'SMTP', $query['email_address']) |
||
| 134 | ); |
||
| 135 | } |
||
| 136 | } else { |
||
| 137 | // all other errors should be propagated to higher level exception handlers |
||
| 138 | throw $e; |
||
| 139 | } |
||
| 140 | } |
||
| 141 | |||
| 142 | $items = array(); |
||
| 143 | if ($rows) { |
||
| 144 | foreach($rows as $user_data) { |
||
| 145 | $item = array(); |
||
| 146 | |||
| 147 | if (!isset($user_data[PR_ACCOUNT])) { |
||
| 148 | $abitem = mapi_ab_openentry($ab, $user_data[PR_ENTRYID]); |
||
| 149 | $user_data = mapi_getprops($abitem, array(PR_ACCOUNT, PR_ADDRTYPE, PR_DISPLAY_NAME, PR_DISPLAY_TYPE_EX, PR_ENTRYID, PR_SEARCH_KEY, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE)); |
||
| 150 | } |
||
| 151 | |||
| 152 | if($excludeGABGroups && $user_data[PR_OBJECT_TYPE] === MAPI_DISTLIST) { |
||
| 153 | // exclude groups from result |
||
| 154 | continue; |
||
| 155 | } |
||
| 156 | |||
| 157 | $item = array(); |
||
| 158 | $item['object_type'] = isset($user_data[PR_OBJECT_TYPE]) ? $user_data[PR_OBJECT_TYPE] : MAPI_MAILUSER; |
||
| 159 | $item['entryid'] = isset($user_data[PR_ENTRYID]) ? bin2hex($user_data[PR_ENTRYID]) : ''; |
||
| 160 | $item['display_name'] = isset($user_data[PR_DISPLAY_NAME]) ? $user_data[PR_DISPLAY_NAME] : ''; |
||
| 161 | $item['display_type'] = isset($user_data[PR_DISPLAY_TYPE]) ? $user_data[PR_DISPLAY_TYPE] : DT_MAILUSER; |
||
| 162 | |||
| 163 | // Test whether the GUID in the entryid is from the Contact Provider |
||
| 164 | if($GLOBALS['entryid']->hasContactProviderGUID($item['entryid'])){ |
||
| 165 | // The properties for a Distribution List differs from the other objects |
||
| 166 | if($item['object_type'] == MAPI_DISTLIST) { |
||
| 167 | $item['address_type'] = 'MAPIPDL'; |
||
| 168 | // The email_address is empty for DistList, using display name for resolving |
||
| 169 | $item['email_address'] = $item['display_name']; |
||
| 170 | $item['smtp_address'] = isset($item['smtp_address']) ? $item['smtp_address'] : ''; |
||
| 171 | } else { |
||
| 172 | $item['address_type'] = 'EX'; |
||
| 173 | if (isset($user_data['address_type']) && $user_data['address_type'] === 'EX') { |
||
| 174 | $item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : ''; |
||
| 175 | } else { |
||
| 176 | // Fake being an EX account, since it's actually an SMTP addrtype the email address is in a different property. |
||
| 177 | $item['smtp_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : ''; |
||
| 178 | // Keep the old scenario happy. |
||
| 179 | $item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : ''; |
||
| 180 | } |
||
| 181 | } |
||
| 182 | // It can be considered a GAB entry |
||
| 183 | } else { |
||
| 184 | $item['user_name'] = isset($user_data[PR_ACCOUNT]) ? $user_data[PR_ACCOUNT] : $item['display_name']; |
||
| 185 | $item['display_type_ex'] = isset($user_data[PR_DISPLAY_TYPE_EX]) ? $user_data[PR_DISPLAY_TYPE_EX] : MAPI_MAILUSER; |
||
| 186 | $item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : ''; |
||
| 187 | $item['smtp_address'] = isset($user_data[PR_SMTP_ADDRESS]) ? $user_data[PR_SMTP_ADDRESS] : $item['email_address']; |
||
| 188 | $item['address_type'] = isset($user_data[PR_ADDRTYPE]) ? $user_data[PR_ADDRTYPE] : 'SMTP'; |
||
| 189 | } |
||
| 190 | |||
| 191 | if (isset($user_data[PR_SEARCH_KEY])) { |
||
| 192 | $item['search_key'] = bin2hex($user_data[PR_SEARCH_KEY]); |
||
| 193 | } else { |
||
| 194 | $emailAddress = isset($item['smtp_address']) ? $item['smtp_address'] : $item['email_address']; |
||
| 195 | $item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $emailAddress)) . '00'; |
||
| 196 | } |
||
| 197 | |||
| 198 | array_push($items, $item); |
||
| 199 | } |
||
| 200 | } |
||
| 201 | |||
| 202 | return $items; |
||
| 203 | } |
||
| 204 | |||
| 205 | /** |
||
| 206 | * Used to find multiple entries from the contact folders in the Addressbook when resolving |
||
| 207 | * returned an ambiguous result. It will find the Contact folders in the Addressbook and |
||
| 208 | * apply a restriction to extract the entries. |
||
| 209 | * @param {MAPIAddressbook} $ab The addressbook |
||
| 210 | * @param {String} $query The search query, case is ignored |
||
| 211 | * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving |
||
| 212 | */ |
||
| 213 | function getAmbigiousContactResolveResults($ab, $query, $excludeGABGroups) |
||
| 214 | { |
||
| 215 | /* We need to look for the Contact folders at the bottom of the following tree. |
||
| 216 | * |
||
| 217 | * IAddrBook |
||
| 218 | * - Root Container |
||
| 219 | * - HIERARCHY TABLE |
||
| 220 | * - Contacts Folders (Contact Container) |
||
| 221 | * - HIERARCHY TABLE (Contact Container Hierarchy) |
||
| 222 | * - Contact folder 1 |
||
| 223 | * - Contact folder 2 |
||
| 224 | **/ |
||
| 225 | |||
| 226 | $rows = Array(); |
||
| 227 | $contactFolderRestriction = $this->getAmbigiousContactRestriction($query, $excludeGABGroups, PR_EMAIL_ADDRESS); |
||
| 228 | // Open the AB Root Container by not supplying an entryid |
||
| 229 | $abRootContainer = mapi_ab_openentry($ab); |
||
| 230 | |||
| 231 | // Get the 'Contact Folders' |
||
| 232 | $hierarchyTable = mapi_folder_gethierarchytable($abRootContainer, MAPI_DEFERRED_ERRORS); |
||
| 233 | $abHierarchyRows = mapi_table_queryallrows($hierarchyTable, array(PR_AB_PROVIDER_ID, PR_ENTRYID)); |
||
| 234 | |||
| 235 | // Look for the 'Contacts Folders' |
||
| 236 | for($i=0,$len=count($abHierarchyRows);$i<$len;$i++){ |
||
| 237 | // Check if the folder matches the Contact Provider GUID |
||
| 238 | if($abHierarchyRows[$i][PR_AB_PROVIDER_ID] == MUIDZCSAB){ |
||
| 239 | $abContactContainerEntryid = $abHierarchyRows[$i][PR_ENTRYID]; |
||
| 240 | break; |
||
| 241 | } |
||
| 242 | } |
||
| 243 | |||
| 244 | // Next go into the 'Contacts Folders' and look in the hierarchy table for the Contact folders. |
||
| 245 | if($abContactContainerEntryid){ |
||
| 246 | // Get the rows from hierarchy table of the 'Contacts Folders' |
||
| 247 | $abContactContainer = mapi_ab_openentry($ab, $abContactContainerEntryid); |
||
| 248 | $abContactContainerHierarchyTable = mapi_folder_gethierarchytable($abContactContainer, MAPI_DEFERRED_ERRORS); |
||
| 249 | $abContactContainerHierarchyRows = mapi_table_queryallrows($abContactContainerHierarchyTable, array(PR_DISPLAY_NAME, PR_OBJECT_TYPE, PR_ENTRYID)); |
||
| 250 | |||
| 251 | // Loop through all the contact folders found under the 'Contacts Folders' hierarchy |
||
| 252 | for($j=0,$len=count($abContactContainerHierarchyRows);$j<$len;$j++){ |
||
| 253 | |||
| 254 | // Open, get contents table, restrict, sort and then merge the result in the list of $rows |
||
| 255 | $abContactFolder = mapi_ab_openentry($ab, $abContactContainerHierarchyRows[$j][PR_ENTRYID]); |
||
| 256 | $abContactFolderTable = mapi_folder_getcontentstable($abContactFolder, MAPI_DEFERRED_ERRORS); |
||
| 257 | |||
| 258 | mapi_table_restrict($abContactFolderTable, $contactFolderRestriction, TBL_BATCH); |
||
| 259 | mapi_table_sort($abContactFolderTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH); |
||
| 260 | |||
| 261 | // Go go gadget, merge! |
||
| 262 | $rows = array_merge($rows, mapi_table_queryallrows($abContactFolderTable, array(PR_ACCOUNT, PR_DISPLAY_NAME, PR_ENTRYID, PR_OBJECT_TYPE, PR_SMTP_ADDRESS, PR_DISPLAY_TYPE_EX, PR_EMAIL_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE))); |
||
| 263 | } |
||
| 264 | } |
||
| 265 | |||
| 266 | return $rows; |
||
| 267 | } |
||
| 268 | |||
| 269 | /** |
||
| 270 | * Setup the restriction used for resolving in the Contact folders or GAB. |
||
| 271 | * @param {String} $query The search query, case is ignored |
||
| 272 | * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving |
||
| 273 | * @param {Number} $content the PROPTAG to search in. |
||
| 274 | */ |
||
| 275 | function getAmbigiousContactRestriction($query, $excludeGABGroups, $content) |
||
| 276 | { |
||
| 277 | // only return users from who the displayName or the username starts with $name |
||
| 278 | // TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and $content. |
||
| 279 | $resAnd = array( |
||
| 280 | array(RES_OR, |
||
| 281 | array( |
||
| 282 | array(RES_CONTENT, |
||
| 283 | array( |
||
| 284 | FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE, |
||
| 285 | ULPROPTAG => PR_DISPLAY_NAME, |
||
| 286 | VALUE => $query |
||
| 287 | ) |
||
| 288 | ), |
||
| 289 | array(RES_CONTENT, |
||
| 290 | array( |
||
| 291 | FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE, |
||
| 292 | ULPROPTAG => $content, |
||
| 293 | VALUE => $query |
||
| 294 | ) |
||
| 295 | ) |
||
| 296 | ) // RES_OR |
||
| 297 | ) |
||
| 298 | ); |
||
| 299 | |||
| 300 | // create restrictions based on excludeGABGroups flag |
||
| 301 | if($excludeGABGroups) { |
||
| 302 | array_push($resAnd, array( |
||
| 303 | RES_PROPERTY, |
||
| 304 | array( |
||
| 305 | RELOP => RELOP_EQ, |
||
| 306 | ULPROPTAG => PR_OBJECT_TYPE, |
||
| 307 | VALUE => MAPI_MAILUSER |
||
| 308 | ) |
||
| 309 | )); |
||
| 310 | } else { |
||
| 311 | array_push($resAnd, array(RES_OR, |
||
| 312 | array( |
||
| 313 | array(RES_PROPERTY, |
||
| 314 | array( |
||
| 315 | RELOP => RELOP_EQ, |
||
| 316 | ULPROPTAG => PR_OBJECT_TYPE, |
||
| 317 | VALUE => MAPI_MAILUSER |
||
| 318 | ) |
||
| 319 | ), |
||
| 320 | array(RES_PROPERTY, |
||
| 321 | array( |
||
| 322 | RELOP => RELOP_EQ, |
||
| 323 | ULPROPTAG => PR_OBJECT_TYPE, |
||
| 324 | VALUE => MAPI_DISTLIST |
||
| 325 | ) |
||
| 326 | ) |
||
| 327 | ) |
||
| 328 | )); |
||
| 329 | } |
||
| 330 | $restriction = array(RES_AND, $resAnd); |
||
| 331 | |||
| 332 | return $restriction; |
||
| 333 | } |
||
| 334 | |||
| 335 | /** |
||
| 336 | * Function does customization of exception based on module data. |
||
| 337 | * like, here it will generate display message based on actionType |
||
| 338 | * for particular exception. |
||
| 339 | * |
||
| 340 | * @param object $e Exception object |
||
| 341 | * @param string $actionType the action type, sent by the client |
||
| 342 | * @param MAPIobject $store Store object of message. |
||
| 343 | * @param string $parententryid parent entryid of the message. |
||
| 344 | * @param string $entryid entryid of the message. |
||
| 345 | * @param array $action the action data, sent by the client |
||
| 346 | */ |
||
| 347 | function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) |
||
| 348 | { |
||
| 349 | if(is_null($e->displayMessage)) { |
||
| 350 | switch($actionType) |
||
| 351 | { |
||
| 352 | case 'checknames': |
||
| 353 | if($e->getCode() == MAPI_E_NO_ACCESS) { |
||
| 354 | $e->setDisplayMessage(_('You have insufficient privileges to perform this action.')); |
||
| 355 | } else { |
||
| 356 | $e->setDisplayMessage(_('Could not resolve user.')); |
||
| 357 | } |
||
| 358 | break; |
||
| 359 | } |
||
| 360 | } |
||
| 361 | |||
| 362 | parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action); |
||
| 363 | } |
||
| 364 | } |
||
| 365 | ?> |
||
|
0 ignored issues
–
show
|
|||
| 366 |
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.
A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.