source: trunk/zpush/backend/zarafa/exporter.php @ 7589

Revision 7589, 11.5 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

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