Back to index

d-push  2.0
exporter.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   exporter.php
00004 * Project   :   Z-Push
00005 * Descr     :   This is a generic class that is
00006 *               used by both the proxy importer
00007 *               (for outgoing messages) and our
00008 *               local importer (for incoming
00009 *               messages). Basically all shared
00010 *               conversion data for converting
00011 *               to and from MAPI objects is in here.
00012 *
00013 * Created   :   14.02.2011
00014 *
00015 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00016 *
00017 * This program is free software: you can redistribute it and/or modify
00018 * it under the terms of the GNU Affero General Public License, version 3,
00019 * as published by the Free Software Foundation with the following additional
00020 * term according to sec. 7:
00021 *
00022 * According to sec. 7 of the GNU Affero General Public License, version 3,
00023 * the terms of the AGPL are supplemented with the following terms:
00024 *
00025 * "Zarafa" is a registered trademark of Zarafa B.V.
00026 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00027 * The licensing of the Program under the AGPL does not imply a trademark license.
00028 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00029 *
00030 * However, if you propagate an unmodified version of the Program you are
00031 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00032 * Furthermore you may use our trademarks where it is necessary to indicate
00033 * the intended purpose of a product or service provided you use it in accordance
00034 * with honest practices in industrial or commercial matters.
00035 * If you want to propagate modified versions of the Program under the name "Z-Push",
00036 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00037 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00038 *
00039 * This program is distributed in the hope that it will be useful,
00040 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00041 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00042 * GNU Affero General Public License for more details.
00043 *
00044 * You should have received a copy of the GNU Affero General Public License
00045 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00046 *
00047 * Consult LICENSE file for details
00048 ************************************************/
00049 
00050 
00056 class ExportChangesICS implements IExportChanges{
00057     private $folderid;
00058     private $store;
00059     private $session;
00060     private $restriction;
00061     private $contentparameters;
00062     private $flags;
00063     private $exporterflags;
00064     private $exporter;
00065 
00076     public function ExportChangesICS($session, $store, $folderid = false) {
00077         // Open a hierarchy or a contents exporter depending on whether a folderid was specified
00078         $this->session = $session;
00079         $this->folderid = $folderid;
00080         $this->store = $store;
00081         $this->restriction = false;
00082 
00083         try {
00084             if($folderid) {
00085                 $entryid = mapi_msgstore_entryidfromsourcekey($store, $folderid);
00086             }
00087             else {
00088                 $storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
00089                 $entryid = $storeprops[PR_IPM_SUBTREE_ENTRYID];
00090             }
00091 
00092             $folder = false;
00093             if ($entryid)
00094                 $folder = mapi_msgstore_openentry($this->store, $entryid);
00095 
00096             // Get the actual ICS exporter
00097             if($folderid) {
00098                 if ($folder)
00099                     $this->exporter = mapi_openproperty($folder, PR_CONTENTS_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
00100                 else
00101                     $this->exporter = false;
00102             }
00103             else {
00104                 $this->exporter = mapi_openproperty($folder, PR_HIERARCHY_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
00105             }
00106         }
00107         catch (MAPIException $me) {
00108             $this->exporter = false;
00109             // We return the general error SYNC_FSSTATUS_CODEUNKNOWN (12) which is also SYNC_STATUS_FOLDERHIERARCHYCHANGED (12)
00110             // if this happened while doing content sync, the mobile will try to resync the folderhierarchy
00111             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);
00112         }
00113     }
00114 
00125     public function Config($state, $flags = 0) {
00126         $this->exporterflags = 0;
00127         $this->flags = $flags;
00128 
00129         // this should never happen
00130         if ($this->exporter === false || is_array($state))
00131             throw new StatusException("ExportChangesICS->Config(): Error, exporter not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
00132 
00133         // change exporterflags if we are doing a ContentExport
00134         if($this->folderid) {
00135             $this->exporterflags |= SYNC_NORMAL | SYNC_READ_STATE;
00136 
00137             // Initial sync, we don't want deleted items. If the initial sync is chunked
00138             // we check the change ID of the syncstate (0 at initial sync)
00139             // On subsequent syncs, we do want to receive delete events.
00140             if(strlen($state) == 0 || bin2hex(substr($state,4,4)) == "00000000") {
00141                 if (!($this->flags & BACKEND_DISCARD_DATA))
00142                     ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesICS->Config(): synching inital data");
00143                 $this->exporterflags |= SYNC_NO_SOFT_DELETIONS | SYNC_NO_DELETIONS;
00144             }
00145         }
00146 
00147         if($this->flags & BACKEND_DISCARD_DATA)
00148             $this->exporterflags |= SYNC_CATCHUP;
00149 
00150         // Put the state information in a stream that can be used by ICS
00151         $stream = mapi_stream_create();
00152         if(strlen($state) == 0)
00153             $state = hex2bin("0000000000000000");
00154 
00155         if (!($this->flags & BACKEND_DISCARD_DATA))
00156             ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->Config() initialized with state: 0x%s", bin2hex($state)));
00157 
00158         mapi_stream_write($stream, $state);
00159         $this->statestream = $stream;
00160     }
00161 
00171     public function ConfigContentParameters($contentparameters){
00172         $filtertype = $contentparameters->GetFilterType();
00173         switch($contentparameters->GetContentClass()) {
00174             case "Email":
00175                 $this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetEmailRestriction(Utils::GetCutOffDate($filtertype)) : false;
00176                 break;
00177             case "Calendar":
00178                 $this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetCalendarRestriction($this->store, Utils::GetCutOffDate($filtertype)) : false;
00179                 break;
00180             default:
00181             case "Contacts":
00182             case "Tasks":
00183                 $this->restriction = false;
00184                 break;
00185         }
00186 
00187         $this->contentParameters = $contentparameters;
00188     }
00189 
00190 
00201     public function InitializeExporter(&$importer) {
00202         // Because we're using ICS, we need to wrap the given importer to make it suitable to pass
00203         // to ICS. We do this in two steps: first, wrap the importer with our own PHP importer class
00204         // which removes all MAPI dependency, and then wrap that class with a C++ wrapper so we can
00205         // pass it to ICS
00206 
00207         // this should never happen!
00208         if($this->exporter === false || !isset($this->statestream) || !isset($this->flags) || !isset($this->exporterflags) ||
00209             ($this->folderid && !isset($this->contentParameters)) )
00210             throw new StatusException("ExportChangesICS->InitializeExporter(): Error, exporter or essential data not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
00211 
00212         // PHP wrapper
00213         $phpwrapper = new PHPWrapper($this->session, $this->store, $importer);
00214 
00215         // with a folderid we are going to get content
00216         if($this->folderid) {
00217             $phpwrapper->ConfigContentParameters($this->contentParameters);
00218 
00219             // ICS c++ wrapper
00220             $mapiimporter = mapi_wrap_importcontentschanges($phpwrapper);
00221             $includeprops = false;
00222         }
00223         else {
00224             $mapiimporter = mapi_wrap_importhierarchychanges($phpwrapper);
00225             $includeprops = array(PR_SOURCE_KEY, PR_DISPLAY_NAME);
00226         }
00227 
00228         if (!$mapiimporter)
00229             throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_wrap_import_*_changes() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
00230 
00231         $ret = mapi_exportchanges_config($this->exporter, $this->statestream, $this->exporterflags, $mapiimporter, $this->restriction, $includeprops, false, 1);
00232         if(!$ret)
00233             throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_exportchanges_config() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
00234 
00235         $changes = mapi_exportchanges_getchangecount($this->exporter);
00236         if($changes || !($this->flags & BACKEND_DISCARD_DATA))
00237             ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->InitializeExporter() successfully. %d changes ready to sync.", $changes));
00238 
00239         return $ret;
00240     }
00241 
00242 
00250     public function GetState() {
00251         $error = false;
00252         if(!isset($this->statestream) || $this->exporter === false)
00253             $error = true;
00254 
00255         if($error === true || mapi_exportchanges_updatestate($this->exporter, $this->statestream) != true )
00256             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);
00257 
00258         mapi_stream_seek($this->statestream, 0, STREAM_SEEK_SET);
00259 
00260         $state = "";
00261         while(true) {
00262             $data = mapi_stream_read($this->statestream, 4096);
00263             if(strlen($data))
00264                 $state .= $data;
00265             else
00266                 break;
00267         }
00268 
00269         return $state;
00270     }
00271 
00278      public function GetChangeCount() {
00279         if ($this->exporter)
00280             return mapi_exportchanges_getchangecount($this->exporter);
00281         else
00282             return 0;
00283     }
00284 
00291     public function Synchronize() {
00292         if ($this->exporter) {
00293             return mapi_exportchanges_synchronize($this->exporter);
00294         }
00295             return false;
00296     }
00297 }
00298 ?>