2016-05-11 10:40:44 +02:00
|
|
|
<?php
|
|
|
|
|
2016-08-01 09:53:37 +02:00
|
|
|
require_once(BASE_PATH . 'server/includes/core/class.encryptionstore.php');
|
2016-12-06 06:52:40 +01:00
|
|
|
require_once('zpushprops.php');
|
2016-05-11 10:40:44 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* PluginMDMModule Module
|
|
|
|
*/
|
|
|
|
class PluginMDMModule extends Module
|
|
|
|
{
|
|
|
|
private $server = '';
|
|
|
|
private $username = '';
|
|
|
|
private $password = '';
|
|
|
|
|
2016-12-06 06:52:40 +01:00
|
|
|
// content data
|
|
|
|
const FOLDERUUID = 1;
|
|
|
|
const FOLDERTYPE = 2;
|
|
|
|
const FOLDERBACKENDID = 5;
|
|
|
|
|
2016-05-11 10:40:44 +02:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
* @param int $id unique id.
|
|
|
|
* @param array $data list of all actions.
|
|
|
|
*/
|
2016-08-01 09:51:29 +02:00
|
|
|
function __construct($id, $data)
|
2016-05-11 10:40:44 +02:00
|
|
|
{
|
2016-08-01 09:51:29 +02:00
|
|
|
parent::__construct($id, $data);
|
2016-05-11 10:40:44 +02:00
|
|
|
|
|
|
|
$this->server = (PLUGIN_MDM_SERVER_SSL ? 'https://' : 'http://') . PLUGIN_MDM_SERVER;
|
|
|
|
|
2016-08-01 09:53:37 +02:00
|
|
|
// Get the username and password from the Encryption store
|
|
|
|
$encryptionStore = EncryptionStore::getInstance();
|
|
|
|
$this->username = $encryptionStore->get('username');
|
|
|
|
$this->password = $encryptionStore->get('password');
|
2016-05-11 10:40:44 +02:00
|
|
|
|
|
|
|
$this->url = $this->server .'/Microsoft-Server-ActiveSync?Cmd=WebserviceDevice&DeviceId=webservice&DeviceType=webservice&User=' . $this->username;
|
|
|
|
}
|
|
|
|
|
2016-12-07 11:48:14 +01:00
|
|
|
/**
|
|
|
|
* 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
|
2016-12-20 12:59:39 +01:00
|
|
|
return dgettext('plugin_mdm', 'version not available');
|
2016-12-07 11:48:14 +01:00
|
|
|
}
|
|
|
|
|
2016-05-11 10:40:44 +02:00
|
|
|
/**
|
|
|
|
* Helper to setup a client.
|
|
|
|
*/
|
2016-12-07 11:48:14 +01:00
|
|
|
function getSoapClient($url='')
|
2016-05-11 10:40:44 +02:00
|
|
|
{
|
2016-12-07 11:48:14 +01:00
|
|
|
if ( empty($url) ){
|
|
|
|
$url = $this->url;
|
|
|
|
}
|
|
|
|
|
2016-05-11 10:40:44 +02:00
|
|
|
return new SoapClient(null, array(
|
2016-12-07 11:48:14 +01:00
|
|
|
'location' => $url,
|
2016-05-11 10:40:44 +02:00
|
|
|
'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()
|
2016-12-07 11:48:14 +01:00
|
|
|
{
|
2016-05-11 10:40:44 +02:00
|
|
|
$client = $this->getSoapClient();
|
|
|
|
return $client->ListDevicesDetails();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-02-21 08:12:13 +01:00
|
|
|
* function which calls the wipeDevice soap call
|
2016-05-11 10:40:44 +02:00
|
|
|
* @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);
|
|
|
|
}
|
2016-12-07 11:48:14 +01:00
|
|
|
|
2017-09-13 14:12:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2016-05-11 10:40:44 +02:00
|
|
|
/**
|
|
|
|
* 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(
|
2016-12-07 11:48:14 +01:00
|
|
|
'type' => 3,
|
2016-05-11 10:40:44 +02:00
|
|
|
'wipe' => $this->wipeDevice($actionData['deviceid'], $actionData['password'])
|
|
|
|
));
|
|
|
|
$GLOBALS['bus']->addData($this->getResponseData());
|
|
|
|
break;
|
|
|
|
case 'resync':
|
|
|
|
$this->addActionData('resync', array(
|
2016-12-07 11:48:14 +01:00
|
|
|
'type' => 3,
|
2016-05-11 10:40:44 +02:00
|
|
|
'resync' => $this->resyncDevice($actionData['deviceid'])
|
|
|
|
));
|
|
|
|
$GLOBALS['bus']->addData($this->getResponseData());
|
|
|
|
break;
|
|
|
|
case 'remove':
|
|
|
|
$this->addActionData('remove', array(
|
2016-12-07 11:48:14 +01:00
|
|
|
'type' => 3,
|
2016-05-11 10:40:44 +02:00
|
|
|
'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){
|
2016-12-06 06:52:40 +01:00
|
|
|
array_push($items, array('props' => $this->getDeviceProps($device->data)));
|
2016-05-11 10:40:44 +02:00
|
|
|
}
|
|
|
|
$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;
|
2017-04-20 13:49:55 +02:00
|
|
|
|
|
|
|
case 'open':
|
2017-09-13 14:12:57 +02:00
|
|
|
$device = $this->getDeviceDetails($actionData["entryid"]);
|
|
|
|
$item = array("item" => $device);
|
2017-04-20 13:49:55 +02:00
|
|
|
$this->addActionData('item', $item);
|
|
|
|
$GLOBALS['bus']->addData($this->getResponseData());
|
|
|
|
break;
|
2017-09-13 14:12:57 +02:00
|
|
|
case 'save':
|
|
|
|
$this ->saveDevice($actionData);
|
|
|
|
$device = $this->getDeviceDetails($actionData["entryid"]);
|
|
|
|
$item = array("item" => $device);
|
|
|
|
$this->addActionData('update', $item);
|
|
|
|
$GLOBALS['bus']->addData($this->getResponseData());
|
|
|
|
break;
|
2016-05-11 10:40:44 +02:00
|
|
|
default:
|
|
|
|
$this->handleUnknownActionType($actionType);
|
|
|
|
}
|
|
|
|
} catch (SoapFault $fault) {
|
2016-08-11 11:08:54 +02:00
|
|
|
$display_message = dgettext('plugin_mdm', 'Something went wrong.');
|
2016-05-11 10:40:44 +02:00
|
|
|
if ($fault->faultcode === 'HTTP') {
|
|
|
|
if ($fault->getMessage() === "Unauthorized") {
|
2016-08-11 11:08:54 +02:00
|
|
|
$display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Unauthorized.');
|
2016-05-11 10:40:44 +02:00
|
|
|
}
|
|
|
|
if ($fault->getMessage() === "Could not connect to host") {
|
2016-08-11 11:08:54 +02:00
|
|
|
$display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Could not connect to host.');
|
2016-05-11 10:40:44 +02:00
|
|
|
}
|
|
|
|
if ($fault->getMessage() === "Not Found") {
|
2016-08-11 11:08:54 +02:00
|
|
|
$display_message = dgettext('plugin_mdm', 'Unable to connect to Z-Push Server. Not found.');
|
2016-05-11 10:40:44 +02:00
|
|
|
}
|
|
|
|
} else if ($fault->faultcode === "ERROR") {
|
2017-09-13 14:12:57 +02:00
|
|
|
$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");
|
|
|
|
}
|
|
|
|
}
|
2016-05-11 10:40:44 +02:00
|
|
|
$this->sendFeedback(false, array("type" => ERROR_GENERAL, "info" => array('display_message' => $display_message)));
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
2016-08-11 11:08:54 +02:00
|
|
|
$this->sendFeedback(true, array("type" => ERROR_GENERAL, "info" => array('display_message' => dgettext('plugin_mdm', 'Something went wrong'))));
|
2016-05-11 10:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-06 06:52:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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'];
|
2017-04-20 13:49:55 +02:00
|
|
|
$item['message_class'] = "IPM.MDM";
|
2016-12-06 06:52:40 +01:00
|
|
|
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);
|
|
|
|
|
2017-02-21 08:12:13 +01:00
|
|
|
if (isset($contentData[$folderid][self::FOLDERUUID])) {
|
2017-09-13 14:12:57 +02:00
|
|
|
if(isset($synchedFolderTypes[$folderType])){
|
|
|
|
$synchedFolderTypes[$folderType]++;
|
|
|
|
} else {
|
|
|
|
$synchedFolderTypes[$folderType] = 1;
|
|
|
|
}
|
2017-02-21 08:12:13 +01:00
|
|
|
}
|
2016-12-06 06:52:40 +01:00
|
|
|
}
|
|
|
|
}
|
2017-02-21 08:12:13 +01:00
|
|
|
$syncFoldersProps = array();
|
2016-12-06 06:52:40 +01:00
|
|
|
foreach ($synchedFolderTypes as $key => $value) {
|
|
|
|
$synchronizedFolders += $value;
|
2017-02-21 08:12:13 +01:00
|
|
|
$syncFoldersProps[strtolower($key) . 'folder'] = $value;
|
2016-12-06 06:52:40 +01:00
|
|
|
}
|
2017-04-20 13:49:55 +02:00
|
|
|
$client = $this->getSoapClient();
|
|
|
|
$items = $client->AdditionalFolderList($device['deviceid']);
|
|
|
|
$syncFoldersProps['sharedfolders'] = count($items);
|
2017-02-21 08:12:13 +01:00
|
|
|
$syncFoldersProps["shortfolderids"] = $device['hasfolderidmapping'] ? dgettext('plugin_mdm', "Yes") : dgettext('plugin_mdm', "No");
|
2017-04-20 13:49:55 +02:00
|
|
|
$syncFoldersProps['synchronizedfolders'] = $synchronizedFolders + count($items);
|
2016-12-06 06:52:40 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2017-04-20 13:49:55 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2017-09-13 14:12:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-27 09:00:18 +02:00
|
|
|
/**
|
2017-09-13 14:12:57 +02:00
|
|
|
* Gets the hierarchy list of all required stores.
|
|
|
|
* Function which is use to get the hierarchy list with PR_SOURCE_KEY.
|
2018-03-27 09:00:18 +02:00
|
|
|
* @return array the array of all hierarchy folders.
|
|
|
|
*/
|
2017-09-13 14:12:57 +02:00
|
|
|
function getHierarchyList()
|
2018-03-27 09:00:18 +02:00
|
|
|
{
|
|
|
|
$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";
|
2017-09-13 14:12:57 +02:00
|
|
|
} else {
|
2018-03-27 09:00:18 +02:00
|
|
|
$storeUserName = $msgstore_props[PR_USER_NAME];
|
|
|
|
}
|
2017-09-13 14:12:57 +02:00
|
|
|
|
2018-03-27 09:00:18 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-09-13 14:12:57 +02:00
|
|
|
|
2018-03-27 09:00:18 +02:00
|
|
|
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();
|
|
|
|
}
|
2018-05-17 16:09:15 +02:00
|
|
|
|
|
|
|
if ($storeType != ZARAFA_STORE_PUBLIC_GUID) {
|
|
|
|
$this->getSubFolders($subtreeFolder, $store, $properties, $storeData, $storeUserName);
|
|
|
|
} else {
|
|
|
|
$this->getSubFoldersPublic($subtreeFolder, $store, $properties, $storeData, $storeUserName);
|
|
|
|
}
|
2018-03-27 09:00:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2017-09-13 14:12:57 +02:00
|
|
|
|
2018-05-17 16:09:15 +02:00
|
|
|
/**
|
|
|
|
* Helper function to get the subfolders of a Public Store
|
|
|
|
*
|
|
|
|
* @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 getSubFoldersPublic($folder, $store, $properties, &$storeData, $storeUserName)
|
|
|
|
{
|
|
|
|
$expand = Array(
|
|
|
|
Array(
|
|
|
|
'folder' => $folder,
|
|
|
|
'props' => mapi_getprops($folder, Array(PR_ENTRYID, PR_SUBFOLDERS))
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
));
|
|
|
|
|
|
|
|
// CONVENIENT_DEPTH doesn't work on the IPM_SUBTREE, hence we will be recursivly
|
|
|
|
// walking through the hierarchy. However, we have some special folders like the
|
|
|
|
// "Favorites" and "Public Folders" from where we can switch to using
|
|
|
|
// CONVENIENT_DEPTH. Obtain these special cases here.
|
|
|
|
$specialEntryids = mapi_getprops($store, array(
|
|
|
|
PR_IPM_FAVORITES_ENTRYID,
|
|
|
|
PR_IPM_PUBLIC_FOLDERS_ENTRYID
|
|
|
|
));
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
$specialFolder = false;
|
|
|
|
|
|
|
|
// Check if this folder is special...
|
|
|
|
if (!empty($specialEntryids)) {
|
|
|
|
foreach ($specialEntryids as $key => $value) {
|
|
|
|
// No need to do compareEntryId(), the special folders have static
|
|
|
|
// entryids, and can be compared using ===.
|
|
|
|
if (bin2hex($subfolder[PR_ENTRYID]) === bin2hex($value)) {
|
|
|
|
$specialFolder = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]);
|
|
|
|
$subfolder = mapi_getprops($specialFolder, $properties);
|
|
|
|
|
|
|
|
// We found the folder, no need to loop over it next time.
|
|
|
|
unset($specialEntryids[$key]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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]) {
|
|
|
|
if ($specialFolder) {
|
|
|
|
// Special folders can be redirected again to getSubFolders(),
|
2018-08-06 20:25:47 +02:00
|
|
|
$this->getSubFolders($specialFolder, $store, $properties, $storeData, $storeUserName);
|
2018-05-17 16:09:15 +02:00
|
|
|
} else {
|
|
|
|
$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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-13 14:12:57 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
}
|
2016-05-11 10:40:44 +02:00
|
|
|
};
|
|
|
|
?>
|