server = (PLUGIN_MDM_SERVER_SSL ? 'https://' : 'http://') . PLUGIN_MDM_SERVER; // Get the username and password from the Encryption store $encryptionStore = EncryptionStore::getInstance(); $this->username = $encryptionStore->get('username'); $this->password = $encryptionStore->get('password'); $this->url = $this->server .'/Microsoft-Server-ActiveSync?Cmd=WebserviceDevice&DeviceId=webservice&DeviceType=webservice&User=' . $this->username; } /** * Returns the version of the Z-Push server * @return String */ function getServerVersion() { // Make a call to the service, so we can read the version // from the response headers try { $url = $this->server .'/Microsoft-Server-ActiveSync?Cmd=WebserviceInfo&DeviceId=webservice&DeviceType=webservice&User=' . $this->username; $client = $this->getSoapClient($url); return $client->About(); } catch(Exception $e){} // If we can't find a version, we will simply return not available return dgettext('plugin_mdm', 'version not available'); } /** * Helper to setup a client. */ function getSoapClient($url='') { if ( empty($url) ){ $url = $this->url; } return new SoapClient(null, array( 'location' => $url, 'uri' => $this->server, 'trace' => 1, 'login' => $this->username, 'password' => $this->password )); } /** * Function which calls the soap call to do a full resync * @param int $deviceid of phone which has to be resynced * @return json $response object contains the response of the soap request from Z-Push */ function resyncDevice($deviceid) { $client = $this->getSoapClient(); return $client->ResyncDevice($deviceid); } /** * Function which calls the wipeDevice soap call * @param int $deviceid of phone which has to be wiped * @param string $password user password * @return json $response object contains the response of the soap request from Z-Push */ function wipeDevice($deviceid, $password) { if ($password == $this->password) { $client = $this->getSoapClient(); return $client->WipeDevice($deviceid); } else { return false; } } /** * function which calls the ListDeviceDetails soap call * @return array $response array contains a list of devices connected to the users account */ function getDevices() { $client = $this->getSoapClient(); return $client->ListDevicesDetails(); } /** * function which calls the wipeDevice soap call * @param int $deviceid of phone which has to be wiped * @return json $response object contains the response of the soap request from Z-Push */ function removeDevice($deviceid) { $client = $this->getSoapClient(); return $client->RemoveDevice($deviceid); } /** * Function witch is use get details of given device. * @param string $deviceid id of device. * @return array contains device props. */ function getDeviceDetails($deviceid) { $client = $this->getSoapClient(); $deviceRawData = $client->GetDeviceDetails($deviceid); $device = array(); $device['props'] = $this->getDeviceProps($deviceRawData->data); $device["sharedfolders"] = array("item" => $this->getAdditionalFolderList($deviceid)); return $device; } /** * Executes all the actions in the $data variable. * @return boolean true on success of false on fialure. */ function execute() { foreach($this->data as $actionType => $actionData) { if(isset($actionType)) { try { switch($actionType) { case 'wipe': $this->wipeDevice($actionData['deviceid'], $actionData['password']); $this->addActionData('wipe', array( 'type' => 3, 'wipe' => $this->wipeDevice($actionData['deviceid'], $actionData['password']) )); $GLOBALS['bus']->addData($this->getResponseData()); break; case 'resync': $this->addActionData('resync', array( 'type' => 3, 'resync' => $this->resyncDevice($actionData['deviceid']) )); $GLOBALS['bus']->addData($this->getResponseData()); break; case 'remove': $this->addActionData('remove', array( 'type' => 3, 'remove' => $this->removeDevice($actionData['deviceid']) )); $GLOBALS['bus']->addData($this->getResponseData()); break; case 'list': $items = array(); $date['page'] = array(); $rawData = $this->getDevices(); foreach($rawData as $device){ array_push($items, array('props' => $this->getDeviceProps($device->data))); } $data['page']['start'] = 0; $data['page']['rowcount'] = count($rawData); $data['page']['totalrowcount'] = $data['page']['rowcount']; $data = array_merge($data, array('item' => $items)); $this->addActionData('list', $data); $GLOBALS['bus']->addData($this->getResponseData()); break; case 'open': $device = $this->getDeviceDetails($actionData["entryid"]); $item = array("item" => $device); $this->addActionData('item', $item); $GLOBALS['bus']->addData($this->getResponseData()); break; case 'save': $this ->saveDevice($actionData); $device = $this->getDeviceDetails($actionData["entryid"]); $item = array("item" => $device); $this->addActionData('update', $item); $GLOBALS['bus']->addData($this->getResponseData()); break; default: $this->handleUnknownActionType($actionType); } } catch (SoapFault $fault) { $display_message = dgettext('plugin_mdm', 'Something went wrong.'); if ($fault->faultcode === 'HTTP') { if ($fault->getMessage() === "Unauthorized") { $display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Unauthorized.'); } if ($fault->getMessage() === "Could not connect to host") { $display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Could not connect to host.'); } if ($fault->getMessage() === "Not Found") { $display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Not found.'); } } else if ($fault->faultcode === "ERROR") { $errors = (explode(": ", $fault->getMessage())); switch ($errors[0]) { case "ASDevice->AddAdditionalFolder()": case "ZPushAdmin::AdditionalFolderAdd()": $display_message = dgettext('plugin_mdm', "Folder can not be added because there is already an additional folder with the same name"); break; case "ASDevice->RemoveAdditionalFolder()": case "ZPushAdmin::AdditionalFolderRemove()": $display_message = dgettext('plugin_mdm', "Folder can not be removed because there is no folder known with given folder id"); break; default: $display_message = dgettext('plugin_mdm', "Device ID could not be found"); } } $this->sendFeedback(false, array("type" => ERROR_GENERAL, "info" => array('display_message' => $display_message))); } catch (Exception $e) { $this->sendFeedback(true, array("type" => ERROR_GENERAL, "info" => array('display_message' => dgettext('plugin_mdm', 'Something went wrong')))); } } } } /** * Function which is use to get device properties. * * @param array $device array of device properties * @return array */ function getDeviceProps($device) { $item = array(); $propsList = ['devicetype', 'deviceos', 'devicefriendlyname', 'useragent', 'asversion', 'firstsynctime', 'lastsynctime', 'lastupdatetime', 'wipestatus', 'policyname', 'koeversion', 'koebuild', 'koebuilddate']; $item['entryid'] = $device['deviceid']; $item['message_class'] = "IPM.MDM"; foreach ($propsList as $prop) { if (isset($device[$prop])) { $item[$prop] = $device[$prop]; } } $item = array_merge($item, $this->getSyncFoldersProps($device)); return $item; } /** * Function which is use to gather some statistics about synchronized folders. * @param array $device array of device props * @return array $syncFoldersProps has list of properties related to synchronized folders */ function getSyncFoldersProps($device) { $contentData = $device['contentdata']; $folders = array_keys($contentData); $synchedFolderTypes = array(); $synchronizedFolders = 0; $hierarchyCache = isset($device['hierarchycache']) ? $device['hierarchycache'] : false; foreach ($folders as $folderid) { if (isset($contentData[$folderid][self::FOLDERUUID]) || isset($device['hierarchyuuid'])) { $type = $contentData[$folderid][self::FOLDERTYPE]; $name = "unknown"; if ($hierarchyCache) { if (array_key_exists($folderid, $hierarchyCache->cacheById)) { $folder = $hierarchyCache->cacheById[$folderid]; } else { $folder = $hierarchyCache->cacheByIdOld[$folderid]; } if ($folder) { $name = $folder->displayname; } } $folderType = $this->getSyncFolderType($type, $name); if (isset($contentData[$folderid][self::FOLDERUUID])) { if(isset($synchedFolderTypes[$folderType])){ $synchedFolderTypes[$folderType]++; } else { $synchedFolderTypes[$folderType] = 1; } } } } $syncFoldersProps = array(); foreach ($synchedFolderTypes as $key => $value) { $synchronizedFolders += $value; $syncFoldersProps[strtolower($key) . 'folder'] = $value; } $client = $this->getSoapClient(); $items = $client->AdditionalFolderList($device['deviceid']); $syncFoldersProps['sharedfolders'] = count($items); $syncFoldersProps["shortfolderids"] = $device['hasfolderidmapping'] ? dgettext('plugin_mdm', "Yes") : dgettext('plugin_mdm', "No"); $syncFoldersProps['synchronizedfolders'] = $synchronizedFolders + count($items); return $syncFoldersProps; } /** * Function which is use to get general type like Mail,Calendar,Contacts,etc. from folder type. * @param int $type foldertype for a folder already known to the mobile * @param string $name folder name * @return string general folder type */ function getSyncFolderType($type, $name) { switch ($type) { case SYNC_FOLDER_TYPE_APPOINTMENT: case SYNC_FOLDER_TYPE_USER_APPOINTMENT: if (KOE_GAB_NAME != "" && $name == KOE_GAB_NAME) { $folderType = "GAB"; } else { $folderType = "Calendars"; } break; case SYNC_FOLDER_TYPE_CONTACT: case SYNC_FOLDER_TYPE_USER_CONTACT: $folderType = "Contacts"; break; case SYNC_FOLDER_TYPE_TASK: case SYNC_FOLDER_TYPE_USER_TASK: $folderType = "Tasks"; break; case SYNC_FOLDER_TYPE_NOTE: case SYNC_FOLDER_TYPE_USER_NOTE: $folderType = "Notes"; break; default: $folderType = "Emails"; break; } return $folderType; } /** * Function which is use to get list of additional folders which was shared with given device * @param string $devid device id * @return array has list of properties related to shared folders */ function getAdditionalFolderList($devid) { $stores = $GLOBALS["mapisession"]->getAllMessageStores(); $client = $this->getSoapClient(); $items = $client->AdditionalFolderList($devid); $data = array(); foreach ($items as $item) { foreach ($stores as $store) { try { $entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($item->folderid)); } catch (MAPIException $me) { continue; } } if (isset($entryid)) { $item->entryid = bin2hex($entryid); } array_push($data, array("props" => $item)); } return $data; } /** * Function which is use to remove additional folder which was shared with given device. * @param string $entryId id of device. * @param string $folderid id of folder which will remove from device. */ function additionalFolderRemove($entryId, $folderid) { $client = $this->getSoapClient(); $client->AdditionalFolderRemove($entryId, $folderid); } /** * Function which is use to add additional folder which will share with given device. * @param string $entryId id of device. * @param array $folder folder which will share with device. */ function additionalFolderAdd($entryId, $folder) { $client = $this->getSoapClient(); $containerClass = isset($folder[PR_CONTAINER_CLASS]) ? $folder[PR_CONTAINER_CLASS] : "IPF.Note"; $folderId = bin2hex($folder[PR_SOURCE_KEY]); $userName = $folder["user"]; $folderName = $userName === "SYSTEM" ? $folder[PR_DISPLAY_NAME] : $folder[PR_DISPLAY_NAME]." - ".$userName; $folderType = $this->getFolderTypeFromContainerClass($containerClass); $client->AdditionalFolderAdd($entryId, $userName, $folderId, $folderName, $folderType, FLD_FLAGS_REPLYASUSER); } /** * Function which use to save the device. * It will use to add or remove folders in the device. * @param array $data array of added and removed folders. */ function saveDevice($data) { $entryid = $data["entryid"]; if (isset($data['sharedfolders'])) { if (isset($data['sharedfolders']['remove'])) { $deletedFolders = $data['sharedfolders']['remove']; foreach ($deletedFolders as $folder) { $this->additionalFolderRemove($entryid, $folder["folderid"]); } } if (isset($data['sharedfolders']['add'])) { $addFolders = $data['sharedfolders']['add']; $hierarchyFolders = $this->getHierarchyList(); foreach ($addFolders as $folder) { foreach ($hierarchyFolders as $hierarchyFolder) { $folderEntryid = bin2hex($hierarchyFolder[PR_ENTRYID]); if ($folderEntryid === $folder["entryid"]) { $this->additionalFolderAdd($entryid, $hierarchyFolder); continue 2; } } } } } } /** * Gets the hierarchy list of all required stores. * Function which is use to get the hierarchy list with PR_SOURCE_KEY. * @return array the array of all hierarchy folders. */ function getHierarchyList() { $storeList = $GLOBALS["mapisession"]->getAllMessageStores(); $properties = $GLOBALS["properties"]->getFolderListProperties(); $otherUsers = $GLOBALS["mapisession"]->retrieveOtherUsersFromSettings(); $properties["source_key"] = PR_SOURCE_KEY; $openWholeStore = true; $storeData = array(); foreach ($storeList as $store) { $msgstore_props = mapi_getprops($store, array(PR_MDB_PROVIDER, PR_ENTRYID, PR_IPM_SUBTREE_ENTRYID, PR_USER_NAME)); $storeType = $msgstore_props[PR_MDB_PROVIDER]; if ($storeType == ZARAFA_SERVICE_GUID) { continue; } else if ($storeType == ZARAFA_STORE_DELEGATE_GUID) { $storeUserName = $GLOBALS["mapisession"]->getUserNameOfStore($msgstore_props[PR_ENTRYID]); } else if ($storeType == ZARAFA_STORE_PUBLIC_GUID) { $storeUserName = "SYSTEM"; } else { $storeUserName = $msgstore_props[PR_USER_NAME]; } if (is_array($otherUsers)) { if (isset($otherUsers[$storeUserName])) { $sharedFolders = $otherUsers[$storeUserName]; if (!isset($otherUsers[$storeUserName]['all'])) { $openWholeStore = false; $a = $this->getSharedFolderList($store, $sharedFolders, $properties, $storeUserName); $storeData = array_merge($storeData, $a); } } } if ($openWholeStore) { if (isset($msgstore_props[PR_IPM_SUBTREE_ENTRYID])) { $subtreeFolderEntryID = $msgstore_props[PR_IPM_SUBTREE_ENTRYID]; try { $subtreeFolder = mapi_msgstore_openentry($store, $subtreeFolderEntryID); } catch (MAPIException $e) { // We've handled the event $e->setHandled(); } $this->getSubFolders($subtreeFolder, $store, $properties, $storeData, $storeUserName); } } } return $storeData; } /** * Helper function to get the shared folder list. * @param object $store Message Store Object. * @param object $sharedFolders Mapi Folder Object. * @param array $properties MAPI property mappings for folders. * @param string $storeUserName owner name of store. * @return array shared folders list. */ function getSharedFolderList($store, $sharedFolders, $properties, $storeUserName) { $msgstore_props = mapi_getprops($store, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_OBJECT_TYPE, PR_STORE_SUPPORT_MASK, PR_MAILBOX_OWNER_ENTRYID, PR_MAILBOX_OWNER_NAME, PR_USER_ENTRYID, PR_USER_NAME, PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD, PR_QUOTA_RECEIVE_THRESHOLD, PR_MESSAGE_SIZE_EXTENDED, PR_MAPPING_SIGNATURE, PR_COMMON_VIEWS_ENTRYID, PR_FINDER_ENTRYID)); $storeData = array(); $folders = array(); try { $inbox = mapi_msgstore_getreceivefolder($store); $inboxProps = mapi_getprops($inbox, array(PR_ENTRYID)); } catch (MAPIException $e) { // don't propogate this error to parent handlers, if store doesn't support it if ($e->getCode() === MAPI_E_NO_SUPPORT) { $e->setHandled(); } } $root = mapi_msgstore_openentry($store, null); $rootProps = mapi_getprops($root, array(PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS)); $additional_ren_entryids = array(); if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) { $additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS]; } $defaultfolders = array( "default_folder_inbox" => array("inbox" => PR_ENTRYID), "default_folder_outbox" => array("store" => PR_IPM_OUTBOX_ENTRYID), "default_folder_sent" => array("store" => PR_IPM_SENTMAIL_ENTRYID), "default_folder_wastebasket" => array("store" => PR_IPM_WASTEBASKET_ENTRYID), "default_folder_favorites" => array("store" => PR_IPM_FAVORITES_ENTRYID), "default_folder_publicfolders" => array("store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID), "default_folder_calendar" => array("root" => PR_IPM_APPOINTMENT_ENTRYID), "default_folder_contact" => array("root" => PR_IPM_CONTACT_ENTRYID), "default_folder_drafts" => array("root" => PR_IPM_DRAFTS_ENTRYID), "default_folder_journal" => array("root" => PR_IPM_JOURNAL_ENTRYID), "default_folder_note" => array("root" => PR_IPM_NOTE_ENTRYID), "default_folder_task" => array("root" => PR_IPM_TASK_ENTRYID), "default_folder_junk" => array("additional" => 4), "default_folder_syncissues" => array("additional" => 1), "default_folder_conflicts" => array("additional" => 0), "default_folder_localfailures" => array("additional" => 2), "default_folder_serverfailures" => array("additional" => 3), ); foreach ($defaultfolders as $key => $prop) { $tag = reset($prop); $from = key($prop); switch ($from) { case "inbox": if (isset($inboxProps[$tag])) { $storeData["props"][$key] = bin2hex($inboxProps[$tag]); } break; case "store": if (isset($msgstore_props[$tag])) { $storeData["props"][$key] = bin2hex($msgstore_props[$tag]); } break; case "root": if (isset($rootProps[$tag])) { $storeData["props"][$key] = bin2hex($rootProps[$tag]); } break; case "additional": if (isset($additional_ren_entryids[$tag])) { $storeData["props"][$key] = bin2hex($additional_ren_entryids[$tag]); } break; } } $store_access = true; $openSubFolders = false; foreach ($sharedFolders as $type => $sharedFolder) { $openSubFolders = ($sharedFolder["show_subfolders"] == true); $folderEntryID = hex2bin($storeData["props"]["default_folder_" . $sharedFolder["folder_type"]]); try { // load folder props $folder = mapi_msgstore_openentry($store, $folderEntryID); } catch (MAPIException $e) { // Indicate that we don't have access to the store, // so no more attempts to read properties or open entries. $store_access = false; // We've handled the event $e->setHandled(); } } if ($store_access === true) { $folderProps = mapi_getprops($folder, $properties); $folderProps["user"] = $storeUserName; array_push($folders, $folderProps); //If folder has sub folders then add its. if ($openSubFolders === true) { if ($folderProps[PR_SUBFOLDERS] != false) { $subFoldersData = array(); $this->getSubFolders($folder, $store, $properties, $subFoldersData, $storeUserName); $folders =array_merge($folders, $subFoldersData); } } } return $folders; } /** * Helper function to get the sub folders of a given folder. * * @access private * @param object $folder Mapi Folder Object. * @param object $store Message Store Object * @param array $properties MAPI property mappings for folders * @param array $storeData Reference to an array. The folder properties are added to this array. * @param string $storeUserName owner name of store. */ function getSubFolders($folder, $store, $properties, &$storeData, $storeUserName) { /** * remove hidden folders, folders with PR_ATTR_HIDDEN property set * should not be shown to the client */ $restriction = Array(RES_OR, Array( Array(RES_PROPERTY, Array( RELOP => RELOP_EQ, ULPROPTAG => PR_ATTR_HIDDEN, VALUE => Array(PR_ATTR_HIDDEN => false) ) ), Array(RES_NOT, Array( Array(RES_EXIST, Array( ULPROPTAG => PR_ATTR_HIDDEN ) ) ) ) )); $expand = Array( Array( 'folder' => $folder, 'props' => mapi_getprops($folder, Array(PR_ENTRYID, PR_SUBFOLDERS)) ) ); // Start looping through the $expand array, during each loop we grab the first item in // the array and obtain the hierarchy table for that particular folder. If one of those // subfolders has subfolders of its own, it will be appended to $expand again to ensure // it will be expanded later. while (!empty($expand)) { $item = array_shift($expand); $columns = $properties; $hierarchyTable = mapi_folder_gethierarchytable($item['folder'], MAPI_DEFERRED_ERRORS); mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH); mapi_table_setcolumns($hierarchyTable, $columns); $columns = null; // Load the hierarchy in small batches $batchcount = 100; do { $rows = mapi_table_queryrows($hierarchyTable, $columns, 0, $batchcount); foreach ($rows as $subfolder) { // If the subfolders has subfolders of its own, append the folder // to the $expand array, so it can be expanded in the next loop. if ($subfolder[PR_SUBFOLDERS]) { $folderObject = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]); array_push($expand, array('folder' => $folderObject, 'props' => $subfolder)); } $subfolder["user"] = $storeUserName; // Add the folder to the return list. array_push($storeData, $subfolder); } // When the server returned a different number of rows then was requested, // we have reached the end of the table and we should exit the loop. } while (count($rows) === $batchcount); } } /** * Function which is use get folder types from the container class * @param string $containerClass container class of folder * @return int folder type */ function getFolderTypeFromContainerClass($containerClass) { switch ($containerClass) { case "IPF.Note": return SYNC_FOLDER_TYPE_USER_MAIL; case "IPF.Appointment": return SYNC_FOLDER_TYPE_USER_APPOINTMENT; case "IPF.Contact": return SYNC_FOLDER_TYPE_USER_CONTACT; case "IPF.StickyNote": return SYNC_FOLDER_TYPE_USER_NOTE; case "IPF.Task": return SYNC_FOLDER_TYPE_USER_TASK; case "IPF.Journal": return SYNC_FOLDER_TYPE_USER_JOURNAL; default: return SYNC_FOLDER_TYPE_UNKNOWN; } } }; ?>