1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
|
<?php
/***********************************************
* File : exporter.php
* Project : Z-Push
* Descr : This is a generic class that is
* used by both the proxy importer
* (for outgoing messages) and our
* local importer (for incoming
* messages). Basically all shared
* conversion data for converting
* to and from MAPI objects is in here.
*
* Created : 14.02.2011
*
* Copyright 2007 - 2011 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* This is our ICS exporter which requests the actual exporter from ICS and makes sure
* that the ImportProxies are used.
*/
class ExportChangesICS implements IExportChanges{
private $folderid;
private $store;
private $session;
private $restriction;
private $contentparameters;
private $flags;
private $exporterflags;
private $exporter;
/**
* Constructor
*
* @param mapisession $session
* @param mapistore $store
* @param string (opt)
*
* @access public
* @throws StatusException
*/
public function ExportChangesICS($session, $store, $folderid = false) {
// Open a hierarchy or a contents exporter depending on whether a folderid was specified
$this->session = $session;
$this->folderid = $folderid;
$this->store = $store;
$this->restriction = false;
try {
if($folderid) {
$entryid = mapi_msgstore_entryidfromsourcekey($store, $folderid);
}
else {
$storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
$entryid = $storeprops[PR_IPM_SUBTREE_ENTRYID];
}
$folder = false;
if ($entryid)
$folder = mapi_msgstore_openentry($this->store, $entryid);
// Get the actual ICS exporter
if($folderid) {
if ($folder)
$this->exporter = mapi_openproperty($folder, PR_CONTENTS_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
else
$this->exporter = false;
}
else {
$this->exporter = mapi_openproperty($folder, PR_HIERARCHY_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
}
}
catch (MAPIException $me) {
$this->exporter = false;
// We return the general error SYNC_FSSTATUS_CODEUNKNOWN (12) which is also SYNC_STATUS_FOLDERHIERARCHYCHANGED (12)
// if this happened while doing content sync, the mobile will try to resync the folderhierarchy
throw new StatusException(sprintf("ExportChangesICS('%s','%s','%s'): Error, unable to open folder: 0x%X", $session, $store, Utils::PrintAsString($folderid), mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN);
}
}
/**
* Configures the exporter
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function Config($state, $flags = 0) {
$this->exporterflags = 0;
$this->flags = $flags;
// this should never happen
if ($this->exporter === false || is_array($state))
throw new StatusException("ExportChangesICS->Config(): Error, exporter not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
// change exporterflags if we are doing a ContentExport
if($this->folderid) {
$this->exporterflags |= SYNC_NORMAL | SYNC_READ_STATE;
// Initial sync, we don't want deleted items. If the initial sync is chunked
// we check the change ID of the syncstate (0 at initial sync)
// On subsequent syncs, we do want to receive delete events.
if(strlen($state) == 0 || bin2hex(substr($state,4,4)) == "00000000") {
if (!($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesICS->Config(): synching inital data");
$this->exporterflags |= SYNC_NO_SOFT_DELETIONS | SYNC_NO_DELETIONS;
}
}
if($this->flags & BACKEND_DISCARD_DATA)
$this->exporterflags |= SYNC_CATCHUP;
// Put the state information in a stream that can be used by ICS
$stream = mapi_stream_create();
if(strlen($state) == 0)
$state = hex2bin("0000000000000000");
if (!($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->Config() initialized with state: 0x%s", bin2hex($state)));
mapi_stream_write($stream, $state);
$this->statestream = $stream;
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters){
$filtertype = $contentparameters->GetFilterType();
switch($contentparameters->GetContentClass()) {
case "Email":
$this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetEmailRestriction(Utils::GetCutOffDate($filtertype)) : false;
break;
case "Calendar":
$this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetCalendarRestriction($this->store, Utils::GetCutOffDate($filtertype)) : false;
break;
default:
case "Contacts":
case "Tasks":
$this->restriction = false;
break;
}
$this->contentParameters = $contentparameters;
}
/**
* Sets the importer the exporter will sent it's changes to
* and initializes the Exporter
*
* @param object &$importer Implementation of IImportChanges
*
* @access public
* @return boolean
* @throws StatusException
*/
public function InitializeExporter(&$importer) {
// Because we're using ICS, we need to wrap the given importer to make it suitable to pass
// to ICS. We do this in two steps: first, wrap the importer with our own PHP importer class
// which removes all MAPI dependency, and then wrap that class with a C++ wrapper so we can
// pass it to ICS
// this should never happen!
if($this->exporter === false || !isset($this->statestream) || !isset($this->flags) || !isset($this->exporterflags) ||
($this->folderid && !isset($this->contentParameters)) )
throw new StatusException("ExportChangesICS->InitializeExporter(): Error, exporter or essential data not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
// PHP wrapper
$phpwrapper = new PHPWrapper($this->session, $this->store, $importer);
// with a folderid we are going to get content
if($this->folderid) {
$phpwrapper->ConfigContentParameters($this->contentParameters);
// ICS c++ wrapper
$mapiimporter = mapi_wrap_importcontentschanges($phpwrapper);
$includeprops = false;
}
else {
$mapiimporter = mapi_wrap_importhierarchychanges($phpwrapper);
$includeprops = array(PR_SOURCE_KEY, PR_DISPLAY_NAME);
}
if (!$mapiimporter)
throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_wrap_import_*_changes() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
$ret = mapi_exportchanges_config($this->exporter, $this->statestream, $this->exporterflags, $mapiimporter, $this->restriction, $includeprops, false, 1);
if(!$ret)
throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_exportchanges_config() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
$changes = mapi_exportchanges_getchangecount($this->exporter);
if($changes || !($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->InitializeExporter() successfully. %d changes ready to sync.", $changes));
return $ret;
}
/**
* Reads the current state from the Exporter
*
* @access public
* @return string
* @throws StatusException
*/
public function GetState() {
$error = false;
if(!isset($this->statestream) || $this->exporter === false)
$error = true;
if($error === true || mapi_exportchanges_updatestate($this->exporter, $this->statestream) != true )
throw new StatusException(sprintf("ExportChangesICS->GetState(): Error, state not available or unable to update: 0x%X", mapi_last_hresult()), (($this->folderid)?SYNC_STATUS_FOLDERHIERARCHYCHANGED:SYNC_FSSTATUS_CODEUNKNOWN), null, LOGLEVEL_WARN);
mapi_stream_seek($this->statestream, 0, STREAM_SEEK_SET);
$state = "";
while(true) {
$data = mapi_stream_read($this->statestream, 4096);
if(strlen($data))
$state .= $data;
else
break;
}
return $state;
}
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount() {
if ($this->exporter)
return mapi_exportchanges_getchangecount($this->exporter);
else
return 0;
}
/**
* Synchronizes a change
*
* @access public
* @return array
*/
public function Synchronize() {
if ($this->exporter) {
return mapi_exportchanges_synchronize($this->exporter);
}
return false;
}
}
?>
|