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

Revision 7589, 111.7 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      :   mapiprovider.php
4* Project   :   Z-Push
5* Descr     :
6*
7* Created   :   14.02.2011
8*
9* Copyright 2007 - 2012 Zarafa Deutschland GmbH
10*
11* This program is free software: you can redistribute it and/or modify
12* it under the terms of the GNU Affero General Public License, version 3,
13* as published by the Free Software Foundation with the following additional
14* term according to sec. 7:
15*
16* According to sec. 7 of the GNU Affero General Public License, version 3,
17* the terms of the AGPL are supplemented with the following terms:
18*
19* "Zarafa" is a registered trademark of Zarafa B.V.
20* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
21* The licensing of the Program under the AGPL does not imply a trademark license.
22* Therefore any rights, title and interest in our trademarks remain entirely with us.
23*
24* However, if you propagate an unmodified version of the Program you are
25* allowed to use the term "Z-Push" to indicate that you distribute the Program.
26* Furthermore you may use our trademarks where it is necessary to indicate
27* the intended purpose of a product or service provided you use it in accordance
28* with honest practices in industrial or commercial matters.
29* If you want to propagate modified versions of the Program under the name "Z-Push",
30* you may only do so if you have a written permission by Zarafa Deutschland GmbH
31* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
32*
33* This program is distributed in the hope that it will be useful,
34* but WITHOUT ANY WARRANTY; without even the implied warranty of
35* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36* GNU Affero General Public License for more details.
37*
38* You should have received a copy of the GNU Affero General Public License
39* along with this program.  If not, see <http://www.gnu.org/licenses/>.
40*
41* Consult LICENSE file for details
42************************************************/
43
44class MAPIProvider {
45    private $session;
46    private $store;
47    private $zRFC822;
48    private $addressbook;
49
50    /**
51     * Constructor of the MAPI Provider
52     * Almost all methods of this class require a MAPI session and/or store
53     *
54     * @param ressource         $session
55     * @param ressource         $store
56     *
57     * @access public
58     */
59    function MAPIProvider($session, $store) {
60        $this->session = $session;
61        $this->store = $store;
62    }
63
64
65    /**----------------------------------------------------------------------------------------------------------
66     * GETTER
67     */
68
69    /**
70     * Reads a message from MAPI
71     * Depending on the message class, a contact, appointment, task or email is read
72     *
73     * @param mixed             $mapimessage
74     * @param ContentParameters $contentparameters
75     *
76     * @access public
77     * @return SyncObject
78     */
79    public function GetMessage($mapimessage, $contentparameters) {
80        // Gets the Sync object from a MAPI object according to its message class
81
82        $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS));
83        if(isset($props[PR_MESSAGE_CLASS]))
84            $messageclass = $props[PR_MESSAGE_CLASS];
85        else
86            $messageclass = "IPM";
87
88        if(strpos($messageclass,"IPM.Contact") === 0)
89            return $this->getContact($mapimessage, $contentparameters);
90        else if(strpos($messageclass,"IPM.Appointment") === 0)
91            return $this->getAppointment($mapimessage, $contentparameters);
92        else if(strpos($messageclass,"IPM.Task") === 0)
93            return $this->getTask($mapimessage, $contentparameters);
94        else if(strpos($messageclass,"IPM.StickyNote") === 0)
95            return $this->getNote($mapimessage, $contentparameters);
96        else
97            return $this->getEmail($mapimessage, $contentparameters);
98    }
99
100    /**
101     * Reads a contact object from MAPI
102     *
103     * @param mixed             $mapimessage
104     * @param ContentParameters $contentparameters
105     *
106     * @access private
107     * @return SyncContact
108     */
109    private function getContact($mapimessage, $contentparameters) {
110        $message = new SyncContact();
111
112        // Standard one-to-one mappings first
113        $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetContactMapping());
114
115        // Contact specific props
116        $contactproperties = MAPIMapping::GetContactProperties();
117        $messageprops = $this->getProps($mapimessage, $contactproperties);
118
119        //set the body according to contentparameters and supported AS version
120        $this->setMessageBody($mapimessage, $contentparameters, $message);
121
122        //check the picture
123        if (isset($messageprops[$contactproperties["haspic"]]) && $messageprops[$contactproperties["haspic"]]) {
124            // Add attachments
125            $attachtable = mapi_message_getattachmenttable($mapimessage);
126            mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
127            $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE));
128
129            foreach($rows as $row) {
130                if(isset($row[PR_ATTACH_NUM])) {
131                    if (isset($row[PR_ATTACH_SIZE]) && $row[PR_ATTACH_SIZE] < MAX_EMBEDDED_SIZE) {
132                        $mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
133                        $message->picture = base64_encode(mapi_attach_openbin($mapiattach, PR_ATTACH_DATA_BIN));
134                    }
135                }
136            }
137        }
138
139        return $message;
140    }
141
142    /**
143     * Reads a task object from MAPI
144     *
145     * @param mixed             $mapimessage
146     * @param ContentParameters $contentparameters
147     *
148     * @access private
149     * @return SyncTask
150     */
151    private function getTask($mapimessage, $contentparameters) {
152        $message = new SyncTask();
153
154        // Standard one-to-one mappings first
155        $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetTaskMapping());
156
157        // Task specific props
158        $taskproperties = MAPIMapping::GetTaskProperties();
159        $messageprops = $this->getProps($mapimessage, $taskproperties);
160
161        //set the body according to contentparameters and supported AS version
162        $this->setMessageBody($mapimessage, $contentparameters, $message);
163
164        //task with deadoccur is an occurrence of a recurring task and does not need to be handled as recurring
165        //webaccess does not set deadoccur for the initial recurring task
166        if(isset($messageprops[$taskproperties["isrecurringtag"]]) &&
167            $messageprops[$taskproperties["isrecurringtag"]] &&
168            (!isset($messageprops[$taskproperties["deadoccur"]]) ||
169            (isset($messageprops[$taskproperties["deadoccur"]]) &&
170            !$messageprops[$taskproperties["deadoccur"]]))) {
171            // Process recurrence
172            $message->recurrence = new SyncTaskRecurrence();
173            $this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, false);
174        }
175
176        // when set the task to complete using the WebAccess, the dateComplete property is not set correctly
177        if ($message->complete == 1 && !isset($message->datecompleted))
178            $message->datecompleted = time();
179
180        // if no reminder is set, announce that to the mobile
181        if (!isset($message->reminderset))
182            $message->reminderset = 0;
183
184        return $message;
185    }
186
187    /**
188     * Reads an appointment object from MAPI
189     *
190     * @param mixed             $mapimessage
191     * @param ContentParameters $contentparameters
192     *
193     * @access private
194     * @return SyncAppointment
195     */
196    private function getAppointment($mapimessage, $contentparameters) {
197        $message = new SyncAppointment();
198
199        // Standard one-to-one mappings first
200        $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetAppointmentMapping());
201
202        // Appointment specific props
203        $appointmentprops = MAPIMapping::GetAppointmentProperties();
204        $messageprops = $this->getProps($mapimessage, $appointmentprops);
205
206        //set the body according to contentparameters and supported AS version
207        $this->setMessageBody($mapimessage, $contentparameters, $message);
208
209        // Set reminder time if reminderset is true
210        if(isset($messageprops[$appointmentprops["reminderset"]]) && $messageprops[$appointmentprops["reminderset"]] == true) {
211            if ($messageprops[$appointmentprops["remindertime"]] == 0x5AE980E1)
212                $message->reminder = 15;
213            else
214                $message->reminder = $messageprops[$appointmentprops["remindertime"]];
215        }
216
217        if(!isset($message->uid))
218            $message->uid = bin2hex($messageprops[$appointmentprops["sourcekey"]]);
219        else
220            $message->uid = Utils::GetICalUidFromOLUid($message->uid);
221
222        // Always set organizer information because some devices do not work properly without it
223        if( isset($messageprops[$appointmentprops["representingentryid"]]) &&
224            isset($messageprops[$appointmentprops["representingname"]])) {
225
226            $message->organizeremail = w2u($this->getSMTPAddressFromEntryID($messageprops[$appointmentprops["representingentryid"]]));
227            $message->organizername = w2u($messageprops[$appointmentprops["representingname"]]);
228        }
229
230        if(isset($messageprops[$appointmentprops["timezonetag"]]))
231            $tz = $this->getTZFromMAPIBlob($messageprops[$appointmentprops["timezonetag"]]);
232        else {
233            // set server default timezone (correct timezone should be configured!)
234            $tz = TimezoneUtil::GetFullTZ();
235        }
236        $message->timezone = base64_encode($this->getSyncBlobFromTZ($tz));
237
238        if(isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) {
239            // Process recurrence
240            $message->recurrence = new SyncRecurrence();
241            $this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, $tz);
242        }
243
244        // Do attendees
245        $reciptable = mapi_message_getrecipienttable($mapimessage);
246        // Only get first 256 recipients, to prevent possible load issues.
247        $rows = mapi_table_queryrows($reciptable, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ADDRTYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TYPE), 0, 256);
248
249        // Exception: we do not synchronize appointments with more than 250 attendees
250        if (count($rows) > 250) {
251            $message->id = bin2hex($messageprops[$appointmentprops["sourcekey"]]);
252            $mbe = new SyncObjectBrokenException("Appointment has too many attendees");
253            $mbe->SetSyncObject($message);
254            throw $mbe;
255        }
256
257        if(count($rows) > 0)
258            $message->attendees = array();
259
260        foreach($rows as $row) {
261            $attendee = new SyncAttendee();
262
263            $attendee->name = w2u($row[PR_DISPLAY_NAME]);
264            //smtp address is always a proper email address
265            if(isset($row[PR_SMTP_ADDRESS]))
266                $attendee->email = w2u($row[PR_SMTP_ADDRESS]);
267            elseif (isset($row[PR_ADDRTYPE]) && isset($row[PR_EMAIL_ADDRESS])) {
268                //if address type is SMTP, it's also a proper email address
269                if ($row[PR_ADDRTYPE] == "SMTP")
270                    $attendee->email = w2u($row[PR_EMAIL_ADDRESS]);
271                //if address type is ZARAFA, the PR_EMAIL_ADDRESS contains username
272                elseif ($row[PR_ADDRTYPE] == "ZARAFA") {
273                    $userinfo = mapi_zarafa_getuser_by_name($this->store, $row[PR_EMAIL_ADDRESS]);
274                    if (is_array($userinfo) && isset($userinfo["emailaddress"]))
275                        $attendee->email = w2u($userinfo["emailaddress"]);
276                }
277            }
278
279            //set attendee's status and type if they're available
280            if (isset($row[PR_RECIPIENT_TRACKSTATUS]))
281                $attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS];
282            if (isset($row[PR_RECIPIENT_TYPE]))
283                $attendee->attendeetype = $row[PR_RECIPIENT_TYPE];
284            // Some attendees have no email or name (eg resources), and if you
285            // don't send one of those fields, the phone will give an error ... so
286            // we don't send it in that case.
287            // also ignore the "attendee" if the email is equal to the organizers' email
288            if(isset($attendee->name) && isset($attendee->email) && $attendee->email != "" && (!isset($message->organizeremail) || (isset($message->organizeremail) && $attendee->email != $message->organizeremail)))
289                array_push($message->attendees, $attendee);
290        }
291
292        // Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded
293        if(isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] > 1) {
294            // Work around iOS6 cancellation issue when there are no attendees for this meeting. Just add ourselves as the sole attendee.
295            if(count($message->attendees) == 0) {
296                if (!isset($message->attendees) || !is_array($message->attendees))
297                    $message->attendees = array();
298
299                ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround"));
300                $attendee = new SyncAttendee();
301
302                $meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetAuthUser());
303
304                if (is_array($meinfo)) {
305                    $attendee->email = w2u($meinfo["emailaddress"]);
306                    $attendee->mame = w2u($meinfo["fullname"]);
307                    $attendee->attendeetype = MAPI_TO;
308
309                    array_push($message->attendees, $attendee);
310                }
311            }
312        }
313
314        if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
315
316        return $message;
317    }
318
319    /**
320     * Reads recurrence information from MAPI
321     *
322     * @param mixed             $mapimessage
323     * @param array             $recurprops
324     * @param SyncObject        &$syncMessage       the message
325     * @param SyncObject        &$syncRecurrence    the  recurrence message
326     * @param array             $tz                 timezone information
327     *
328     * @access private
329     * @return
330     */
331    private function getRecurrence($mapimessage, $recurprops, &$syncMessage, &$syncRecurrence, $tz) {
332        if ($syncRecurrence instanceof SyncTaskRecurrence)
333            $recurrence = new TaskRecurrence($this->store, $mapimessage);
334        else
335            $recurrence = new Recurrence($this->store, $mapimessage);
336
337        switch($recurrence->recur["type"]) {
338            case 10: // daily
339                switch($recurrence->recur["subtype"]) {
340                    default:
341                        $syncRecurrence->type = 0;
342                        break;
343                    case 1:
344                        $syncRecurrence->type = 0;
345                        $syncRecurrence->dayofweek = 62; // mon-fri
346                        $syncRecurrence->interval = 1;
347                        break;
348                }
349                break;
350            case 11: // weekly
351                    $syncRecurrence->type = 1;
352                break;
353            case 12: // monthly
354                switch($recurrence->recur["subtype"]) {
355                    default:
356                        $syncRecurrence->type = 2;
357                        break;
358                    case 3:
359                        $syncRecurrence->type = 3;
360                        break;
361                }
362                break;
363            case 13: // yearly
364                switch($recurrence->recur["subtype"]) {
365                    default:
366                        $syncRecurrence->type = 4;
367                        break;
368                    case 2:
369                        $syncRecurrence->type = 5;
370                        break;
371                    case 3:
372                        $syncRecurrence->type = 6;
373                }
374        }
375        // Termination
376        switch($recurrence->recur["term"]) {
377            case 0x21:
378                $syncRecurrence->until = $recurrence->recur["end"];
379                // fixes Mantis #350 : recur-end does not consider timezones - use ClipEnd if available
380                if (isset($recurprops[$recurrence->proptags["enddate_recurring"]]))
381                    $syncRecurrence->until = $recurprops[$recurrence->proptags["enddate_recurring"]];
382                // add one day (minus 1 sec) to the end time to make sure the last occurrence is covered
383                $syncRecurrence->until += 86399;
384                break;
385            case 0x22:
386                $syncRecurrence->occurrences = $recurrence->recur["numoccur"]; break;
387            case 0x23:
388                // never ends
389                break;
390        }
391
392        // Correct 'alldayevent' because outlook fails to set it on recurring items of 24 hours or longer
393        if(isset($recurrence->recur["endocc"], $recurrence->recur["startocc"]) && ($recurrence->recur["endocc"] - $recurrence->recur["startocc"] >= 1440))
394            $syncMessage->alldayevent = true;
395
396        // Interval is different according to the type/subtype
397        switch($recurrence->recur["type"]) {
398            case 10:
399                if($recurrence->recur["subtype"] == 0)
400                    $syncRecurrence->interval = (int)($recurrence->recur["everyn"] / 1440);  // minutes
401                break;
402            case 11:
403            case 12:
404                $syncRecurrence->interval = $recurrence->recur["everyn"];
405                break; // months / weeks
406            case 13:
407                $syncRecurrence->interval = (int)($recurrence->recur["everyn"] / 12);
408                break; // months
409        }
410
411        if(isset($recurrence->recur["weekdays"]))
412            $syncRecurrence->dayofweek = $recurrence->recur["weekdays"]; // bitmask of days (1 == sunday, 128 == saturday
413        if(isset($recurrence->recur["nday"]))
414            $syncRecurrence->weekofmonth = $recurrence->recur["nday"]; // N'th {DAY} of {X} (0-5)
415        if(isset($recurrence->recur["month"]))
416            $syncRecurrence->monthofyear = (int)($recurrence->recur["month"] / (60 * 24 * 29)) + 1; // works ok due to rounding. see also $monthminutes below (1-12)
417        if(isset($recurrence->recur["monthday"]))
418            $syncRecurrence->dayofmonth = $recurrence->recur["monthday"]; // day of month (1-31)
419
420        // All changed exceptions are appointments within the 'exceptions' array. They contain the same items as a normal appointment
421        foreach($recurrence->recur["changed_occurences"] as $change) {
422            $exception = new SyncAppointmentException();
423
424            // start, end, basedate, subject, remind_before, reminderset, location, busystatus, alldayevent, label
425            if(isset($change["start"]))
426                $exception->starttime = $this->getGMTTimeByTZ($change["start"], $tz);
427            if(isset($change["end"]))
428                $exception->endtime = $this->getGMTTimeByTZ($change["end"], $tz);
429            if(isset($change["basedate"])) {
430                $exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz);
431
432                //open body because getting only property might not work because of memory limit
433                $exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]);
434                if($exceptionatt) {
435                    $exceptionobj = mapi_attach_openobj($exceptionatt, 0);
436                    $this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception);
437                }
438            }
439            if(isset($change["subject"]))
440                $exception->subject = w2u($change["subject"]);
441            if(isset($change["reminder_before"]) && $change["reminder_before"])
442                $exception->reminder = $change["remind_before"];
443            if(isset($change["location"]))
444                $exception->location = w2u($change["location"]);
445            if(isset($change["busystatus"]))
446                $exception->busystatus = $change["busystatus"];
447            if(isset($change["alldayevent"]))
448                $exception->alldayevent = $change["alldayevent"];
449
450            // set some data from the original appointment
451            if (isset($syncMessage->uid))
452                $exception->uid = $syncMessage->uid;
453            if (isset($syncMessage->organizername))
454                $exception->organizername = $syncMessage->organizername;
455            if (isset($syncMessage->organizeremail))
456                $exception->organizeremail = $syncMessage->organizeremail;
457
458            if(!isset($syncMessage->exceptions))
459                $syncMessage->exceptions = array();
460
461            array_push($syncMessage->exceptions, $exception);
462        }
463
464        // Deleted appointments contain only the original date (basedate) and a 'deleted' tag
465        foreach($recurrence->recur["deleted_occurences"] as $deleted) {
466            $exception = new SyncAppointmentException();
467
468            $exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($deleted) + $recurrence->recur["startocc"] * 60, $tz);
469            $exception->deleted = "1";
470
471            if(!isset($syncMessage->exceptions))
472                $syncMessage->exceptions = array();
473
474            array_push($syncMessage->exceptions, $exception);
475        }
476
477        if (isset($syncMessage->complete) && $syncMessage->complete) {
478            $syncRecurrence->complete = $syncMessage->complete;
479        }
480    }
481
482    /**
483     * Reads an email object from MAPI
484     *
485     * @param mixed             $mapimessage
486     * @param ContentParameters $contentparameters
487     *
488     * @access private
489     * @return SyncEmail
490     */
491    private function getEmail($mapimessage, $contentparameters) {
492        $message = new SyncMail();
493
494        $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping());
495
496        $emailproperties = MAPIMapping::GetEmailProperties();
497        $messageprops = $this->getProps($mapimessage, $emailproperties);
498
499        if(isset($messageprops[PR_SOURCE_KEY]))
500            $sourcekey = $messageprops[PR_SOURCE_KEY];
501        else
502            return false;
503
504        //set the body according to contentparameters and supported AS version
505        $this->setMessageBody($mapimessage, $contentparameters, $message);
506
507        $fromname = $fromaddr = "";
508
509        if(isset($messageprops[$emailproperties["representingname"]])) {
510            // remove encapsulating double quotes from the representingname
511            $fromname = preg_replace('/^\"(.*)\"$/',"\${1}", $messageprops[$emailproperties["representingname"]]);
512        }
513        if(isset($messageprops[$emailproperties["representingentryid"]]))
514            $fromaddr = $this->getSMTPAddressFromEntryID($messageprops[$emailproperties["representingentryid"]]);
515
516        if($fromname == $fromaddr)
517            $fromname = "";
518
519        if($fromname)
520            $from = "\"" . w2u($fromname) . "\" <" . w2u($fromaddr) . ">";
521        else
522            //START CHANGED dw2412 HTC shows "error" if sender name is unknown
523            $from = "\"" . w2u($fromaddr) . "\" <" . w2u($fromaddr) . ">";
524            //END CHANGED dw2412 HTC shows "error" if sender name is unknown
525
526        $message->from = $from;
527
528        // process Meeting Requests
529        if(isset($message->messageclass) && strpos($message->messageclass, "IPM.Schedule.Meeting") === 0) {
530            $message->meetingrequest = new SyncMeetingRequest();
531            $this->getPropsFromMAPI($message->meetingrequest, $mapimessage, MAPIMapping::GetMeetingRequestMapping());
532
533            $meetingrequestproperties = MAPIMapping::GetMeetingRequestProperties();
534            $props = $this->getProps($mapimessage, $meetingrequestproperties);
535
536            // Get the GOID
537            if(isset($props[$meetingrequestproperties["goidtag"]]))
538                $message->meetingrequest->globalobjid = base64_encode($props[$meetingrequestproperties["goidtag"]]);
539
540            // Set Timezone
541            if(isset($props[$meetingrequestproperties["timezonetag"]]))
542                $tz = $this->getTZFromMAPIBlob($props[$meetingrequestproperties["timezonetag"]]);
543            else
544                $tz = $this->getGMTTZ();
545
546            $message->meetingrequest->timezone = base64_encode($this->getSyncBlobFromTZ($tz));
547
548            // send basedate if exception
549            if(isset($props[$meetingrequestproperties["recReplTime"]]) ||
550                (isset($props[$meetingrequestproperties["lidIsException"]]) && $props[$meetingrequestproperties["lidIsException"]] == true)) {
551                if (isset($props[$meetingrequestproperties["recReplTime"]])){
552                    $basedate = $props[$meetingrequestproperties["recReplTime"]];
553                    $message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $this->getGMTTZ());
554                }
555                else {
556                    if (!isset($props[$meetingrequestproperties["goidtag"]]) || !isset($props[$meetingrequestproperties["recurStartTime"]]) || !isset($props[$meetingrequestproperties["timezonetag"]]))
557                        ZLog::Write(LOGLEVEL_WARN, "Missing property to set correct basedate for exception");
558                    else {
559                        $basedate = Utils::ExtractBaseDate($props[$meetingrequestproperties["goidtag"]], $props[$meetingrequestproperties["recurStartTime"]]);
560                        $message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $tz);
561                    }
562                }
563            }
564
565            // Organizer is the sender
566            $message->meetingrequest->organizer = $message->from;
567
568            // Process recurrence
569            if(isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]]) {
570                $myrec = new SyncMeetingRequestRecurrence();
571                // get recurrence -> put $message->meetingrequest as message so the 'alldayevent' is set correctly
572                $this->getRecurrence($mapimessage, $props, $message->meetingrequest, $myrec, $tz);
573                $message->meetingrequest->recurrences = array($myrec);
574            }
575
576            // Force the 'alldayevent' in the object at all times. (non-existent == 0)
577            if(!isset($message->meetingrequest->alldayevent) || $message->meetingrequest->alldayevent == "")
578                $message->meetingrequest->alldayevent = 0;
579
580            // Instancetype
581            // 0 = single appointment
582            // 1 = master recurring appointment
583            // 2 = single instance of recurring appointment
584            // 3 = exception of recurring appointment
585            $message->meetingrequest->instancetype = 0;
586            if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]] == 1)
587                $message->meetingrequest->instancetype = 1;
588            else if ((!isset($props[$meetingrequestproperties["isrecurringtag"]]) || $props[$meetingrequestproperties["isrecurringtag"]] == 0 )&& isset($message->meetingrequest->recurrenceid))
589                if (isset($props[$meetingrequestproperties["appSeqNr"]]) && $props[$meetingrequestproperties["appSeqNr"]] == 0 )
590                    $message->meetingrequest->instancetype = 2;
591                else
592                    $message->meetingrequest->instancetype = 3;
593
594            // Disable reminder if it is off
595            if(!isset($props[$meetingrequestproperties["reminderset"]]) || $props[$meetingrequestproperties["reminderset"]] == false)
596                $message->meetingrequest->reminder = "";
597            //the property saves reminder in minutes, but we need it in secs
598            else {
599                ///set the default reminder time to seconds
600                if ($props[$meetingrequestproperties["remindertime"]] == 0x5AE980E1)
601                    $message->meetingrequest->reminder = 900;
602                else
603                    $message->meetingrequest->reminder = $props[$meetingrequestproperties["remindertime"]] * 60;
604            }
605
606            // Set sensitivity to 0 if missing
607            if(!isset($message->meetingrequest->sensitivity))
608                $message->meetingrequest->sensitivity = 0;
609
610            // if a meeting request response hasn't been processed yet,
611            // do it so that the attendee status is updated on the mobile
612            if(!isset($messageprops[$emailproperties["processed"]])) {
613                $req = new Meetingrequest($this->store, $mapimessage, $this->session);
614                if ($req->isMeetingRequestResponse()) {
615                    $req->processMeetingRequestResponse();
616                }
617                if ($req->isMeetingCancellation()) {
618                    $req->processMeetingCancellation();
619                }
620            }
621        }
622
623        // Add attachments
624        $attachtable = mapi_message_getattachmenttable($mapimessage);
625        $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM));
626        $entryid = bin2hex($messageprops[$emailproperties["entryid"]]);
627
628        foreach($rows as $row) {
629            if(isset($row[PR_ATTACH_NUM])) {
630                $mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]);
631
632                $attachprops = mapi_getprops($mapiattach, array(PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_W, PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W));
633
634                $stream = mapi_openpropertytostream($mapiattach, PR_ATTACH_DATA_BIN);
635                if($stream) {
636                    $stat = mapi_stream_stat($stream);
637
638                    if (Request::GetProtocolVersion() >= 12.0) {
639                        $attach = new SyncBaseAttachment();
640                    }
641                    else {
642                        $attach = new SyncAttachment();
643                    }
644
645                    // the displayname is handled equal for all AS versions
646                    $attach->displayname = w2u((isset($attachprops[PR_ATTACH_LONG_FILENAME])) ? $attachprops[PR_ATTACH_LONG_FILENAME] : ((isset($attachprops[PR_ATTACH_FILENAME])) ? $attachprops[PR_ATTACH_FILENAME] : "attachment.bin"));
647
648                    // fix attachment name in case of inline images
649                    if ($attach->displayname == "inline.txt" && (isset($attachprops[PR_ATTACH_MIME_TAG]) || $attachprops[PR_ATTACH_MIME_TAG_W])) {
650                        $mimetype = (isset($attachprops[PR_ATTACH_MIME_TAG]))?$attachprops[PR_ATTACH_MIME_TAG]:$attachprops[PR_ATTACH_MIME_TAG_W];
651                        $mime = explode("/", $mimetype);
652
653                        if (count($mime) == 2 && $mime[0] == "image") {
654                            $attach->displayname = "inline." . $mime[1];
655                        }
656                    }
657
658                    // set AS version specific parameters
659                    if (Request::GetProtocolVersion() >= 12.0) {
660                        $attach->filereference = $entryid.":".$row[PR_ATTACH_NUM];
661                        $attach->method = 1;
662                        $attach->estimatedDataSize = $stat["cb"];
663
664                        if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID])
665                            $attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID];
666
667                        if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_W]) && $attachprops[PR_ATTACH_CONTENT_ID_W])
668                            $attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_W];
669
670                        if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) $attach->isinline = 1;
671
672                        if(!isset($message->asattachments))
673                            $message->asattachments = array();
674
675                        array_push($message->asattachments, $attach);
676                    }
677                    else {
678                        $attach->attsize = $stat["cb"];
679                        $attach->attname = $entryid.":".$row[PR_ATTACH_NUM];
680                        if(!isset($message->attachments))
681                            $message->attachments = array();
682
683                        array_push($message->attachments, $attach);
684                    }
685                }
686            }
687        }
688
689        // Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting
690        // in the SMTP addresses as well, while displayto and displaycc could just contain the display names
691        $message->to = array();
692        $message->cc = array();
693
694        $reciptable = mapi_message_getrecipienttable($mapimessage);
695        $rows = mapi_table_queryallrows($reciptable, array(PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID));
696
697        foreach ($rows as $row) {
698            $address = "";
699            $fulladdr = "";
700
701            $addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : "";
702
703            if (isset($row[PR_SMTP_ADDRESS]))
704                $address = $row[PR_SMTP_ADDRESS];
705            elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS]))
706                $address = $row[PR_EMAIL_ADDRESS];
707            elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID]))
708                $address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]);
709
710            $name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "";
711
712            if($name == "" || $name == $address)
713                $fulladdr = w2u($address);
714            else {
715                if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
716                    $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">";
717                }
718                else {
719                    $fulladdr = w2u($name) ."<" . w2u($address) . ">";
720                }
721            }
722
723            if($row[PR_RECIPIENT_TYPE] == MAPI_TO) {
724                array_push($message->to, $fulladdr);
725            } else if($row[PR_RECIPIENT_TYPE] == MAPI_CC) {
726                array_push($message->cc, $fulladdr);
727            }
728        }
729
730        if (is_array($message->to) && !empty($message->to)) $message->to = implode(", ", $message->to);
731        if (is_array($message->cc) && !empty($message->cc)) $message->cc = implode(", ", $message->cc);
732
733        // without importance some mobiles assume "0" (low) - Mantis #439
734        if (!isset($message->importance))
735            $message->importance = IMPORTANCE_NORMAL;
736
737        //TODO contentclass and nativebodytype and internetcpid
738        if (!isset($message->internetcpid)) $message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
739        $this->setFlag($mapimessage, $message);
740        $message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
741        if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
742
743        return $message;
744    }
745
746    /**
747    * Reads a note object from MAPI
748    *
749    * @param mixed             $mapimessage
750    * @param ContentParameters $contentparameters
751    *
752    * @access private
753    * @return SyncNote
754    */
755    private function getNote($mapimessage, $contentparameters) {
756        $message = new SyncNote();
757
758        // Standard one-to-one mappings first
759        $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetNoteMapping());
760
761        //set the body according to contentparameters and supported AS version
762        $this->setMessageBody($mapimessage, $contentparameters, $message);
763
764        return $message;
765    }
766
767    /**
768     * Reads a folder object from MAPI
769     *
770     * @param mixed             $mapimessage
771     *
772     * @access public
773     * @return SyncFolder
774     */
775    public function GetFolder($mapifolder) {
776        $folder = new SyncFolder();
777
778        $folderprops = mapi_getprops($mapifolder, array(PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_ENTRYID, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN));
779        $storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
780
781        if(!isset($folderprops[PR_DISPLAY_NAME]) ||
782           !isset($folderprops[PR_PARENT_ENTRYID]) ||
783           !isset($folderprops[PR_SOURCE_KEY]) ||
784           !isset($folderprops[PR_ENTRYID]) ||
785           !isset($folderprops[PR_PARENT_SOURCE_KEY]) ||
786           !isset($storeprops[PR_IPM_SUBTREE_ENTRYID])) {
787            ZLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties");
788            return false;
789        }
790
791        // ignore hidden folders
792        if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) {
793            ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME]));
794            return false;
795        }
796
797        $folder->serverid = bin2hex($folderprops[PR_SOURCE_KEY]);
798        if($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID])
799            $folder->parentid = "0";
800        else
801            $folder->parentid = bin2hex($folderprops[PR_PARENT_SOURCE_KEY]);
802        $folder->displayname = w2u($folderprops[PR_DISPLAY_NAME]);
803        $folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], isset($folderprops[PR_CONTAINER_CLASS])?$folderprops[PR_CONTAINER_CLASS]:false);
804
805        return $folder;
806    }
807
808    /**
809     * Returns the foldertype for an entryid
810     * Gets the folder type by checking the default folders in MAPI
811     *
812     * @param string            $entryid
813     * @param string            $class      (opt)
814     *
815     * @access public
816     * @return long
817     */
818    public function GetFolderType($entryid, $class = false) {
819        $storeprops = mapi_getprops($this->store, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID));
820        $inbox = mapi_msgstore_getreceivefolder($this->store);
821        $inboxprops = mapi_getprops($inbox, array(PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID));
822
823        if($entryid == $inboxprops[PR_ENTRYID])
824            return SYNC_FOLDER_TYPE_INBOX;
825        if($entryid == $inboxprops[PR_IPM_DRAFTS_ENTRYID])
826            return SYNC_FOLDER_TYPE_DRAFTS;
827        if($entryid == $storeprops[PR_IPM_WASTEBASKET_ENTRYID])
828            return SYNC_FOLDER_TYPE_WASTEBASKET;
829        if($entryid == $storeprops[PR_IPM_SENTMAIL_ENTRYID])
830            return SYNC_FOLDER_TYPE_SENTMAIL;
831        if($entryid == $storeprops[PR_IPM_OUTBOX_ENTRYID])
832            return SYNC_FOLDER_TYPE_OUTBOX;
833        if($entryid == $inboxprops[PR_IPM_TASK_ENTRYID])
834            return SYNC_FOLDER_TYPE_TASK;
835        if($entryid == $inboxprops[PR_IPM_APPOINTMENT_ENTRYID])
836            return SYNC_FOLDER_TYPE_APPOINTMENT;
837        if($entryid == $inboxprops[PR_IPM_CONTACT_ENTRYID])
838            return SYNC_FOLDER_TYPE_CONTACT;
839        if($entryid == $inboxprops[PR_IPM_NOTE_ENTRYID])
840            return SYNC_FOLDER_TYPE_NOTE;
841        if($entryid == $inboxprops[PR_IPM_JOURNAL_ENTRYID])
842            return SYNC_FOLDER_TYPE_JOURNAL;
843
844        // user created folders
845        if ($class == "IPF.Note")
846            return SYNC_FOLDER_TYPE_USER_MAIL;
847        if ($class == "IPF.Task")
848            return SYNC_FOLDER_TYPE_USER_TASK;
849        if ($class == "IPF.Appointment")
850            return SYNC_FOLDER_TYPE_USER_APPOINTMENT;
851        if ($class == "IPF.Contact")
852            return SYNC_FOLDER_TYPE_USER_CONTACT;
853        if ($class == "IPF.StickyNote")
854            return SYNC_FOLDER_TYPE_USER_NOTE;
855        if ($class == "IPF.Journal")
856            return  SYNC_FOLDER_TYPE_USER_JOURNAL;
857
858        return SYNC_FOLDER_TYPE_OTHER;
859    }
860
861    /**
862     * Indicates if the entry id is a default MAPI folder
863     *
864     * @param string            $entryid
865     *
866     * @access public
867     * @return boolean
868     */
869    public function IsMAPIDefaultFolder($entryid) {
870        $msgstore_props = mapi_getprops($this->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_MAILBOX_OWNER_ENTRYID));
871
872        $inboxProps = array();
873        $inbox = mapi_msgstore_getreceivefolder($this->store);
874        if(!mapi_last_hresult())
875            $inboxProps = mapi_getprops($inbox, array(PR_ENTRYID));
876
877        $root = mapi_msgstore_openentry($this->store, null);
878        $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));
879
880        $additional_ren_entryids = array();
881        if(isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]))
882            $additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS];
883
884        $defaultfolders = array(
885                        "inbox"                 =>      array("inbox"=>PR_ENTRYID),
886                        "outbox"                =>      array("store"=>PR_IPM_OUTBOX_ENTRYID),
887                        "sent"                  =>      array("store"=>PR_IPM_SENTMAIL_ENTRYID),
888                        "wastebasket"           =>      array("store"=>PR_IPM_WASTEBASKET_ENTRYID),
889                        "favorites"             =>      array("store"=>PR_IPM_FAVORITES_ENTRYID),
890                        "publicfolders"         =>      array("store"=>PR_IPM_PUBLIC_FOLDERS_ENTRYID),
891                        "calendar"              =>      array("root" =>PR_IPM_APPOINTMENT_ENTRYID),
892                        "contact"               =>      array("root" =>PR_IPM_CONTACT_ENTRYID),
893                        "drafts"                =>      array("root" =>PR_IPM_DRAFTS_ENTRYID),
894                        "journal"               =>      array("root" =>PR_IPM_JOURNAL_ENTRYID),
895                        "note"                  =>      array("root" =>PR_IPM_NOTE_ENTRYID),
896                        "task"                  =>      array("root" =>PR_IPM_TASK_ENTRYID),
897                        "junk"                  =>      array("additional" =>4),
898                        "syncissues"            =>      array("additional" =>1),
899                        "conflicts"             =>      array("additional" =>0),
900                        "localfailures"         =>      array("additional" =>2),
901                        "serverfailures"        =>      array("additional" =>3),
902        );
903
904        foreach($defaultfolders as $key=>$prop){
905            $tag = reset($prop);
906            $from = key($prop);
907            switch($from){
908                case "inbox":
909                    if(isset($inboxProps[$tag]) && $entryid == $inboxProps[$tag]) {
910                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Inbox found, key '%s'", $key));
911                        return true;
912                    }
913                    break;
914                case "store":
915                    if(isset($msgstore_props[$tag]) && $entryid == $msgstore_props[$tag]) {
916                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Store folder found, key '%s'", $key));
917                        return true;
918                    }
919                    break;
920                case "root":
921                    if(isset($rootProps[$tag]) && $entryid == $rootProps[$tag]) {
922                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Root folder found, key '%s'", $key));
923                        return true;
924                    }
925                    break;
926                case "additional":
927                    if(isset($additional_ren_entryids[$tag]) && $entryid == $additional_ren_entryids[$tag]) {
928                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->IsMAPIFolder(): Additional folder found, key '%s'", $key));
929                        return true;
930                    }
931            }
932        }
933        return false;
934    }
935
936    /**----------------------------------------------------------------------------------------------------------
937     * SETTER
938     */
939
940    /**
941     * Writes a SyncObject to MAPI
942     * Depending on the message class, a contact, appointment, task or email is written
943     *
944     * @param mixed             $mapimessage
945     * @param SyncObject        $message
946     *
947     * @access public
948     * @return boolean
949     */
950    public function SetMessage($mapimessage, $message) {
951        // TODO check with instanceof
952        switch(strtolower(get_class($message))) {
953            case "synccontact":
954                return $this->setContact($mapimessage, $message);
955            case "syncappointment":
956                return $this->setAppointment($mapimessage, $message);
957            case "synctask":
958                return $this->setTask($mapimessage, $message);
959            case "syncnote":
960                return $this->setNote($mapimessage, $message);
961            default:
962                //for emails only flag (read and todo) changes are possible
963                return $this->setEmail($mapimessage, $message);
964        }
965    }
966
967    /**
968     * Writes SyncMail to MAPI (actually flags only)
969     *
970     * @param mixed             $mapimessage
971     * @param SyncMail          $message
972     */
973    private function setEmail($mapimessage, $message) {
974        $flagmapping = MAPIMapping::GetMailFlagsMapping();
975        $flagprops = MAPIMapping::GetMailFlagsProperties();
976        $flagprops = array_merge($this->getPropIdsFromStrings($flagmapping), $this->getPropIdsFromStrings($flagprops));
977        // flag specific properties to be set
978        $props = $delprops = array();
979        // unset message flags if:
980        // flag is not set
981        if (empty($message->flag) ||
982            // flag status is not set
983            !isset($message->flag->flagstatus) ||
984            // flag status is 0 or empty
985            (isset($message->flag->flagstatus) && ($message->flag->flagstatus == 0 || $message->flag->flagstatus == "")) ) {
986            // if message flag is empty, some properties need to be deleted
987            // and some set to 0 or false
988
989            $props[$flagprops["todoitemsflags"]] = 0;
990            $props[$flagprops["status"]] = 0;
991            $props[$flagprops["completion"]] = 0.0;
992            $props[$flagprops["flagtype"]] = "";
993            $props[$flagprops["ordinaldate"]] = 0x7fffffff; // ordinal date is 12am 1.1.4501, set it to max possible value
994            $props[$flagprops["subordinaldate"]] = "";
995            $props[$flagprops["replyrequested"]] = false;
996            $props[$flagprops["responserequested"]] = false;
997            $props[$flagprops["reminderset"]] = false;
998            $props[$flagprops["complete"]] = false;
999
1000            $delprops[] = $flagprops["todotitle"];
1001            $delprops[] = $flagprops["duedate"];
1002            $delprops[] = $flagprops["startdate"];
1003            $delprops[] = $flagprops["datecompleted"];
1004            $delprops[] = $flagprops["utcstartdate"];
1005            $delprops[] = $flagprops["utcduedate"];
1006            $delprops[] = $flagprops["completetime"];
1007            $delprops[] = $flagprops["flagstatus"];
1008            $delprops[] = $flagprops["flagicon"];
1009        }
1010        else {
1011            $this->setPropsInMAPI($mapimessage, $message->flag, $flagmapping);
1012            $props[$flagprops["todoitemsflags"]] = 1;
1013            $props[$flagprops["todotitle"]] = $message->subject;
1014            // ordinal date is utc current time
1015            if (!isset($message->flag->ordinaldate) || empty($message->flag->ordinaldate)) {
1016                $props[$flagprops["ordinaldate"]] = time();
1017            }
1018            // the default value
1019            if (!isset($message->flag->subordinaldate) || empty($message->flag->subordinaldate)) {
1020                $props[$flagprops["subordinaldate"]] = "5555555";
1021            }
1022            $props[$flagprops["flagicon"]] = 6; //red flag icon
1023            $props[$flagprops["replyrequested"]] = true;
1024            $props[$flagprops["responserequested"]] = true;
1025
1026            if ($message->flag->flagstatus == SYNC_FLAGSTATUS_COMPLETE) {
1027                $props[$flagprops["status"]] = olTaskComplete;
1028                $props[$flagprops["completion"]] = 1.0;
1029                $props[$flagprops["complete"]] = true;
1030                $props[$flagprops["replyrequested"]] = false;
1031                $props[$flagprops["responserequested"]] = false;
1032                unset($props[$flagprops["flagicon"]]);
1033                $delprops[] = $flagprops["flagicon"];
1034            }
1035        }
1036
1037        if (!empty($props)) {
1038            mapi_setprops($mapimessage, $props);
1039        }
1040        if (!empty($delprops)) {
1041            mapi_deleteprops($mapimessage, $delprops);
1042        }
1043    }
1044
1045    /**
1046     * Writes a SyncAppointment to MAPI
1047     *
1048     * @param mixed             $mapimessage
1049     * @param SyncAppointment   $message
1050     *
1051     * @access private
1052     * @return boolean
1053     */
1054    private function setAppointment($mapimessage, $appointment) {
1055        // Get timezone info
1056        if(isset($appointment->timezone))
1057            $tz = $this->getTZFromSyncBlob(base64_decode($appointment->timezone));
1058        else
1059            $tz = false;
1060
1061        //calculate duration because without it some webaccess views are broken. duration is in min
1062        $localstart = $this->getLocaltimeByTZ($appointment->starttime, $tz);
1063        $localend = $this->getLocaltimeByTZ($appointment->endtime, $tz);
1064        $duration = ($localend - $localstart)/60;
1065
1066        //nokia sends an yearly event with 0 mins duration but as all day event,
1067        //so make it end next day
1068        if ($appointment->starttime == $appointment->endtime && isset($appointment->alldayevent) && $appointment->alldayevent) {
1069            $duration = 1440;
1070            $appointment->endtime = $appointment->starttime + 24 * 60 * 60;
1071            $localend = $localstart + 24 * 60 * 60;
1072        }
1073
1074        // is the transmitted UID OL compatible?
1075        // if not, encapsulate the transmitted uid
1076        $appointment->uid = Utils::GetOLUidFromICalUid($appointment->uid);
1077
1078        mapi_setprops($mapimessage, array(PR_MESSAGE_CLASS => "IPM.Appointment"));
1079
1080        $appointmentmapping = MAPIMapping::GetAppointmentMapping();
1081        $this->setPropsInMAPI($mapimessage, $appointment, $appointmentmapping);
1082        $appointmentprops = MAPIMapping::GetAppointmentProperties();
1083        $appointmentprops = array_merge($this->getPropIdsFromStrings($appointmentmapping), $this->getPropIdsFromStrings($appointmentprops));
1084        //appointment specific properties to be set
1085        $props = array();
1086
1087        //we also have to set the responsestatus and not only meetingstatus, so we use another mapi tag
1088        $props[$appointmentprops["responsestatus"]] = (isset($appointment->responsestatus)) ? $appointment->responsestatus : olResponseNone;
1089
1090        //sensitivity is not enough to mark an appointment as private, so we use another mapi tag
1091        $private = (isset($appointment->sensitivity) && $appointment->sensitivity == 0) ? false : true;
1092
1093        // Set commonstart/commonend to start/end and remindertime to start, duration, private and cleanGlobalObjectId
1094        $props[$appointmentprops["commonstart"]] = $appointment->starttime;
1095        $props[$appointmentprops["commonend"]] = $appointment->endtime;
1096        $props[$appointmentprops["reminderstart"]] = $appointment->starttime;
1097        // Set reminder boolean to 'true' if reminder is set
1098        $props[$appointmentprops["reminderset"]] = isset($appointment->reminder) ? true : false;
1099        $props[$appointmentprops["duration"]] = $duration;
1100        $props[$appointmentprops["private"]] = $private;
1101        $props[$appointmentprops["uid"]] = $appointment->uid;
1102        // Set named prop 8510, unknown property, but enables deleting a single occurrence of a recurring
1103        // type in OLK2003.
1104        $props[$appointmentprops["sideeffects"]] = 369;
1105
1106
1107        if(isset($appointment->reminder) && $appointment->reminder >= 0) {
1108            // Set 'flagdueby' to correct value (start - reminderminutes)
1109            $props[$appointmentprops["flagdueby"]] = $appointment->starttime - $appointment->reminder * 60;
1110            $props[$appointmentprops["remindertime"]] = $appointment->reminder;
1111        }
1112        // unset the reminder
1113        else {
1114            $props[$appointmentprops["reminderset"]] = false;
1115        }
1116
1117        if (isset($appointment->asbody)) {
1118            $this->setASbody($appointment->asbody, $props, $appointmentprops);
1119        }
1120
1121        if(isset($appointment->recurrence)) {
1122            // Set PR_ICON_INDEX to 1025 to show correct icon in category view
1123            $props[$appointmentprops["icon"]] = 1025;
1124
1125            //if there aren't any exceptions, use the 'old style' set recurrence
1126            $noexceptions = true;
1127
1128            $recurrence = new Recurrence($this->store, $mapimessage);
1129            $recur = array();
1130            $this->setRecurrence($appointment, $recur);
1131
1132            // set the recurrence type to that of the MAPI
1133            $props[$appointmentprops["recurrencetype"]] = $recur["recurrencetype"];
1134
1135            $starttime = $this->gmtime($localstart);
1136            $endtime = $this->gmtime($localend);
1137
1138            //set recurrence start here because it's calculated differently for tasks and appointments
1139            $recur["start"] = $this->getDayStartOfTimestamp($this->getGMTTimeByTZ($localstart, $tz));
1140
1141            $recur["startocc"] = $starttime["tm_hour"] * 60 + $starttime["tm_min"];
1142            $recur["endocc"] = $recur["startocc"] + $duration; // Note that this may be > 24*60 if multi-day
1143
1144            //only tasks can regenerate
1145            $recur["regen"] = false;
1146
1147            // Process exceptions. The PDA will send all exceptions for this recurring item.
1148            if(isset($appointment->exceptions)) {
1149                foreach($appointment->exceptions as $exception) {
1150                    // we always need the base date
1151                    if(!isset($exception->exceptionstarttime))
1152                        continue;
1153
1154                    if(isset($exception->deleted) && $exception->deleted) {
1155                        // Delete exception
1156                        if(!isset($recur["deleted_occurences"]))
1157                            $recur["deleted_occurences"] = array();
1158
1159                        array_push($recur["deleted_occurences"], $this->getDayStartOfTimestamp($exception->exceptionstarttime));
1160                    } else {
1161                        // Change exception
1162                        $basedate = $this->getDayStartOfTimestamp($exception->exceptionstarttime);
1163                        $mapiexception = array("basedate" => $basedate);
1164                        //other exception properties which are not handled in recurrence
1165                        $exceptionprops = array();
1166
1167                        if(isset($exception->starttime)) {
1168                            $mapiexception["start"] = $this->getLocaltimeByTZ($exception->starttime, $tz);
1169                            $exceptionprops[$appointmentprops["starttime"]] = $exception->starttime;
1170                        }
1171                        if(isset($exception->endtime)) {
1172                            $mapiexception["end"] = $this->getLocaltimeByTZ($exception->endtime, $tz);
1173                            $exceptionprops[$appointmentprops["endtime"]] = $exception->endtime;
1174                        }
1175                        if(isset($exception->subject))
1176                            $exceptionprops[$appointmentprops["subject"]] = $mapiexception["subject"] = u2w($exception->subject);
1177                        if(isset($exception->location))
1178                            $exceptionprops[$appointmentprops["location"]] = $mapiexception["location"] = u2w($exception->location);
1179                        if(isset($exception->busystatus))
1180                            $exceptionprops[$appointmentprops["busystatus"]] = $mapiexception["busystatus"] = $exception->busystatus;
1181                        if(isset($exception->reminder)) {
1182                            $exceptionprops[$appointmentprops["reminderset"]] = $mapiexception["reminder_set"] = 1;
1183                            $exceptionprops[$appointmentprops["remindertime"]] = $mapiexception["remind_before"] = $exception->reminder;
1184                        }
1185                        if(isset($exception->alldayevent))
1186                            $exceptionprops[$appointmentprops["alldayevent"]] = $mapiexception["alldayevent"] = $exception->alldayevent;
1187
1188
1189                        if(!isset($recur["changed_occurences"]))
1190                            $recur["changed_occurences"] = array();
1191
1192                        if (isset($exception->body))
1193                            $exceptionprops[$appointmentprops["body"]] = u2w($exception->body);
1194
1195                        array_push($recur["changed_occurences"], $mapiexception);
1196
1197                        if (!empty($exceptionprops)) {
1198                            $noexceptions = false;
1199                            if($recurrence->isException($basedate)){
1200                                $recurrence->modifyException($exceptionprops, $basedate);
1201                            }
1202                            else {
1203                                $recurrence->createException($exceptionprops, $basedate);
1204                            }
1205                        }
1206
1207                    }
1208                }
1209            }
1210
1211            //setRecurrence deletes the attachments from an appointment
1212            if ($noexceptions) {
1213                $recurrence->setRecurrence($tz, $recur);
1214            }
1215        }
1216        else {
1217            $props[$appointmentprops["isrecurring"]] = false;
1218        }
1219
1220        //always set the PR_SENT_REPRESENTING_* props so that the attendee status update also works with the webaccess
1221        $p = array( $appointmentprops["representingentryid"], $appointmentprops["representingname"], $appointmentprops["sentrepresentingaddt"],
1222                    $appointmentprops["sentrepresentingemail"], $appointmentprops["sentrepresentinsrchk"]);
1223        $representingprops = $this->getProps($mapimessage, $p);
1224
1225        if (!isset($representingprops[$appointmentprops["representingentryid"]])) {
1226            $props[$appointmentprops["representingname"]] = Request::GetAuthUser();
1227            $props[$appointmentprops["sentrepresentingemail"]] = Request::GetAuthUser();
1228            $props[$appointmentprops["sentrepresentingaddt"]] = "ZARAFA";
1229            $props[$appointmentprops["representingentryid"]] = mapi_createoneoff(Request::GetAuthUser(), "ZARAFA", Request::GetAuthUser());
1230            $props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]].":".$props[$appointmentprops["sentrepresentingemail"]];
1231        }
1232
1233        // Do attendees
1234        if(isset($appointment->attendees) && is_array($appointment->attendees)) {
1235            $recips = array();
1236
1237            // Outlook XP requires organizer in the attendee list as well
1238            $org = array();
1239            $org[PR_ENTRYID] = isset($representingprops[$appointmentprops["representingentryid"]]) ? $representingprops[$appointmentprops["representingentryid"]] : $props[$appointmentprops["representingentryid"]];
1240            $org[PR_DISPLAY_NAME] = isset($representingprops[$appointmentprops["representingname"]]) ? $representingprops[$appointmentprops["representingname"]] : $props[$appointmentprops["representingname"]];
1241            $org[PR_ADDRTYPE] = isset($representingprops[$appointmentprops["sentrepresentingaddt"]]) ? $representingprops[$appointmentprops["sentrepresentingaddt"]] : $props[$appointmentprops["sentrepresentingaddt"]];
1242            $org[PR_EMAIL_ADDRESS] = isset($representingprops[$appointmentprops["sentrepresentingemail"]]) ? $representingprops[$appointmentprops["sentrepresentingemail"]] : $props[$appointmentprops["sentrepresentingemail"]];
1243            $org[PR_SEARCH_KEY] = isset($representingprops[$appointmentprops["sentrepresentinsrchk"]]) ? $representingprops[$appointmentprops["sentrepresentinsrchk"]] : $props[$appointmentprops["sentrepresentinsrchk"]];
1244            $org[PR_RECIPIENT_FLAGS] = recipOrganizer | recipSendable;
1245            $org[PR_RECIPIENT_TYPE] = MAPI_TO;
1246
1247            array_push($recips, $org);
1248
1249            //open addresss book for user resolve
1250            $addrbook = $this->getAddressbook();
1251            foreach($appointment->attendees as $attendee) {
1252                $recip = array();
1253                $recip[PR_EMAIL_ADDRESS] = u2w($attendee->email);
1254
1255                // lookup information in GAB if possible so we have up-to-date name for given address
1256                $userinfo = array( array( PR_DISPLAY_NAME => $recip[PR_EMAIL_ADDRESS] ) );
1257                $userinfo = mapi_ab_resolvename($addrbook, $userinfo, EMS_AB_ADDRESS_LOOKUP);
1258                if(mapi_last_hresult() == NOERROR) {
1259                    $recip[PR_DISPLAY_NAME] = $userinfo[0][PR_DISPLAY_NAME];
1260                    $recip[PR_EMAIL_ADDRESS] = $userinfo[0][PR_EMAIL_ADDRESS];
1261                    $recip[PR_SEARCH_KEY] = $userinfo[0][PR_SEARCH_KEY];
1262                    $recip[PR_ADDRTYPE] = $userinfo[0][PR_ADDRTYPE];
1263                    $recip[PR_ENTRYID] = $userinfo[0][PR_ENTRYID];
1264                    $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1265                    $recip[PR_RECIPIENT_FLAGS] = recipSendable;
1266                }
1267                else {
1268                    $recip[PR_DISPLAY_NAME] = u2w($attendee->name);
1269                    $recip[PR_SEARCH_KEY] = "SMTP:".$recip[PR_EMAIL_ADDRESS]."\0";
1270                    $recip[PR_ADDRTYPE] = "SMTP";
1271                    $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1272                    $recip[PR_ENTRYID] = mapi_createoneoff($recip[PR_DISPLAY_NAME], $recip[PR_ADDRTYPE], $recip[PR_EMAIL_ADDRESS]);
1273                }
1274
1275                array_push($recips, $recip);
1276            }
1277
1278            mapi_message_modifyrecipients($mapimessage, 0, $recips);
1279            $props[$appointmentprops["icon"]] = 1026;
1280            $props[$appointmentprops["mrwassent"]] = true;
1281        }
1282        mapi_setprops($mapimessage, $props);
1283    }
1284
1285    /**
1286     * Writes a SyncContact to MAPI
1287     *
1288     * @param mixed             $mapimessage
1289     * @param SyncContact       $contact
1290     *
1291     * @access private
1292     * @return boolean
1293     */
1294    private function setContact($mapimessage, $contact) {
1295        mapi_setprops($mapimessage, array(PR_MESSAGE_CLASS => "IPM.Contact"));
1296
1297        // normalize email addresses
1298        if (isset($contact->email1address) && (($contact->email1address = $this->extractEmailAddress($contact->email1address)) === false))
1299            unset($contact->email1address);
1300
1301        if (isset($contact->email2address) && (($contact->email2address = $this->extractEmailAddress($contact->email2address)) === false))
1302            unset($contact->email2address);
1303
1304        if (isset($contact->email3address) && (($contact->email3address = $this->extractEmailAddress($contact->email3address)) === false))
1305            unset($contact->email3address);
1306
1307        $contactmapping = MAPIMapping::GetContactMapping();
1308        $contactprops = MAPIMapping::GetContactProperties();
1309        $this->setPropsInMAPI($mapimessage, $contact, $contactmapping);
1310
1311        ///set display name from contact's properties
1312        $cname = $this->composeDisplayName($contact);
1313
1314        //get contact specific mapi properties and merge them with the AS properties
1315        $contactprops = array_merge($this->getPropIdsFromStrings($contactmapping), $this->getPropIdsFromStrings($contactprops));
1316
1317        //contact specific properties to be set
1318        $props = array();
1319
1320        //need to be set in order to show contacts properly in outlook and wa
1321        $nremails = array();
1322        $abprovidertype = 0;
1323
1324        if (isset($contact->email1address))
1325            $this->setEmailAddress($contact->email1address, $cname, 1, $props, $contactprops, $nremails, $abprovidertype);
1326        if (isset($contact->email2address))
1327            $this->setEmailAddress($contact->email2address, $cname, 2, $props, $contactprops, $nremails, $abprovidertype);
1328        if (isset($contact->email3address))
1329            $this->setEmailAddress($contact->email3address, $cname, 3, $props, $contactprops, $nremails, $abprovidertype);
1330
1331        $props[$contactprops["addressbooklong"]] = $abprovidertype;
1332        $props[$contactprops["displayname"]] = $props[$contactprops["subject"]] = $cname;
1333
1334        //pda multiple e-mail addresses bug fix for the contact
1335        if (!empty($nremails)) $props[$contactprops["addressbookmv"]] = $nremails;
1336
1337
1338        //set addresses
1339        $this->setAddress("home", $contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props, $contactprops);
1340        $this->setAddress("business", $contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props, $contactprops);
1341        $this->setAddress("other", $contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props, $contactprops);
1342
1343        //set the mailing address and its type
1344        if (isset($props[$contactprops["businessaddress"]])) {
1345            $props[$contactprops["mailingaddress"]] = 2;
1346            $this->setMailingAddress($contact->businesscity, $contact->businesscountry, $contact->businesspostalcode, $contact->businessstate, $contact->businessstreet, $props[$contactprops["businessaddress"]], $props, $contactprops);
1347        }
1348        elseif (isset($props[$contactprops["homeaddress"]])) {
1349            $props[$contactprops["mailingaddress"]] = 1;
1350            $this->setMailingAddress($contact->homecity, $contact->homecountry, $contact->homepostalcode, $contact->homestate, $contact->homestreet, $props[$contactprops["homeaddress"]], $props, $contactprops);
1351        }
1352        elseif (isset($props[$contactprops["otheraddress"]])) {
1353            $props[$contactprops["mailingaddress"]] = 3;
1354            $this->setMailingAddress($contact->othercity, $contact->othercountry, $contact->otherpostalcode, $contact->otherstate, $contact->otherstreet, $props[$contactprops["otheraddress"]], $props, $contactprops);
1355        }
1356
1357        if (isset($contact->picture)) {
1358            $picbinary = base64_decode($contact->picture);
1359            $picsize = strlen($picbinary);
1360            if ($picsize < MAX_EMBEDDED_SIZE) {
1361                $props[$contactprops["haspic"]] = false;
1362
1363                // TODO contact picture handling
1364                // check if contact has already got a picture. delete it first in that case
1365                // delete it also if it was removed on a mobile
1366                $picprops = mapi_getprops($mapimessage, array($props[$contactprops["haspic"]]));
1367                if (isset($picprops[$props[$contactprops["haspic"]]]) && $picprops[$props[$contactprops["haspic"]]]) {
1368                    ZLog::Write(LOGLEVEL_DEBUG, "Contact already has a picture. Delete it");
1369
1370                    $attachtable = mapi_message_getattachmenttable($mapimessage);
1371                    mapi_table_restrict($attachtable, MAPIUtils::GetContactPicRestriction());
1372                    $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM));
1373                    if (isset($rows) && is_array($rows)) {
1374                        foreach ($rows as $row) {
1375                            mapi_message_deleteattach($mapimessage, $row[PR_ATTACH_NUM]);
1376                        }
1377                    }
1378                }
1379
1380                // only set picture if there's data in the request
1381                if ($picbinary !== false && $picsize > 0) {
1382                    $props[$contactprops["haspic"]] = true;
1383                    $pic = mapi_message_createattach($mapimessage);
1384                    // Set properties of the attachment
1385                    $picprops = array(
1386                        PR_ATTACH_LONG_FILENAME_A => "ContactPicture.jpg",
1387                        PR_DISPLAY_NAME => "ContactPicture.jpg",
1388                        0x7FFF000B => true,
1389                        PR_ATTACHMENT_HIDDEN => false,
1390                        PR_ATTACHMENT_FLAGS => 1,
1391                        PR_ATTACH_METHOD => ATTACH_BY_VALUE,
1392                        PR_ATTACH_EXTENSION_A => ".jpg",
1393                        PR_ATTACH_NUM => 1,
1394                        PR_ATTACH_SIZE => $picsize,
1395                        PR_ATTACH_DATA_BIN => $picbinary,
1396                    );
1397
1398                    mapi_setprops($pic, $picprops);
1399                    mapi_savechanges($pic);
1400                }
1401            }
1402        }
1403
1404        if (isset($contact->asbody)) {
1405            $this->setASbody($contact->asbody, $props, $contactprops);
1406        }
1407
1408        //set fileas
1409        if (defined('FILEAS_ORDER')) {
1410            $lastname = (isset($contact->lastname)) ? $contact->lastname : "";
1411            $firstname = (isset($contact->firstname)) ? $contact->firstname : "";
1412            $middlename = (isset($contact->middlename)) ? $contact->middlename : "";
1413            $company = (isset($contact->companyname)) ? $contact->companyname : "";
1414            $props[$contactprops["fileas"]] = Utils::BuildFileAs($lastname, $firstname, $middlename, $company);
1415        }
1416        else ZLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined");
1417
1418        mapi_setprops($mapimessage, $props);
1419    }
1420
1421    /**
1422     * Writes a SyncTask to MAPI
1423     *
1424     * @param mixed             $mapimessage
1425     * @param SyncTask          $task
1426     *
1427     * @access private
1428     * @return boolean
1429     */
1430    private function setTask($mapimessage, $task) {
1431        mapi_setprops($mapimessage, array(PR_MESSAGE_CLASS => "IPM.Task"));
1432
1433        $taskmapping = MAPIMapping::GetTaskMapping();
1434        $taskprops = MAPIMapping::GetTaskProperties();
1435        $this->setPropsInMAPI($mapimessage, $task, $taskmapping);
1436        $taskprops = array_merge($this->getPropIdsFromStrings($taskmapping), $this->getPropIdsFromStrings($taskprops));
1437
1438        // task specific properties to be set
1439        $props = array();
1440
1441        if (isset($task->asbody)) {
1442            $this->setASbody($task->asbody, $props, $taskprops);
1443        }
1444
1445        if(isset($task->complete)) {
1446            if($task->complete) {
1447                // Set completion to 100%
1448                // Set status to 'complete'
1449                $props[$taskprops["completion"]] = 1.0;
1450                $props[$taskprops["status"]] = 2;
1451            } else {
1452                // Set completion to 0%
1453                // Set status to 'not started'
1454                $props[$taskprops["completion"]] = 0.0;
1455                $props[$taskprops["status"]] = 0;
1456            }
1457        }
1458        if (isset($task->recurrence) && class_exists('TaskRecurrence')) {
1459            $deadoccur = false;
1460            if ((isset($task->recurrence->occurrences) && $task->recurrence->occurrences == 1) ||
1461                (isset($task->recurrence->deadoccur) && $task->recurrence->deadoccur == 1)) //ios5 sends deadoccur inside the recurrence
1462                $deadoccur = true;
1463
1464            // Set PR_ICON_INDEX to 1281 to show correct icon in category view
1465            $props[$taskprops["icon"]] = 1281;
1466            // dead occur - false if new occurrences should be generated from the task
1467            // true - if it is the last ocurrence of the task
1468            $props[$taskprops["deadoccur"]] = $deadoccur;
1469            $props[$taskprops["isrecurringtag"]] = true;
1470
1471            $recurrence = new TaskRecurrence($this->store, $mapimessage);
1472            $recur = array();
1473            $this->setRecurrence($task, $recur);
1474
1475            // task specific recurrence properties which we need to set here
1476            // "start" and "end" are in GMT when passing to class.recurrence
1477            // set recurrence start here because it's calculated differently for tasks and appointments
1478            $recur["start"] = $task->recurrence->start;
1479            $recur["regen"] = $task->regenerate;
1480            //Also add dates to $recur
1481            $recur["duedate"] = $task->duedate;
1482            $recurrence->setRecurrence($recur);
1483        }
1484        mapi_setprops($mapimessage, $props);
1485    }
1486
1487    /**
1488    * Writes a SyncNote to MAPI
1489    *
1490    * @param mixed             $mapimessage
1491    * @param SyncNote          $note
1492    *
1493    * @access private
1494    * @return boolean
1495    */
1496    private function setNote($mapimessage, $note) {
1497        // Touchdown does not send categories if all are unset or there is none.
1498        // Setting it to an empty array will unset the property in Zarafa as well
1499        if (!isset($note->categories)) $note->categories = array();
1500
1501        $this->setPropsInMAPI($mapimessage, $note, MAPIMapping::GetNoteMapping());
1502
1503        $noteprops = MAPIMapping::GetNoteProperties();
1504        $noteprops = $this->getPropIdsFromStrings($noteprops);
1505
1506        // note specific properties to be set
1507        $props = array();
1508        $props[$noteprops["messageclass"]] = "IPM.StickyNote";
1509        // set body otherwise the note will be "broken" when editing it in outlook
1510        $props[$noteprops["body"]] = $note->asbody->data;
1511        mapi_setprops($mapimessage, $props);
1512    }
1513
1514    /**----------------------------------------------------------------------------------------------------------
1515     * HELPER
1516     */
1517
1518    /**
1519     * Returns the tiemstamp offset
1520     *
1521     * @param string            $ts
1522     *
1523     * @access private
1524     * @return long
1525     */
1526    private function GetTZOffset($ts) {
1527        $Offset = date("O", $ts);
1528
1529        $Parity = $Offset < 0 ? -1 : 1;
1530        $Offset = $Parity * $Offset;
1531        $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1532
1533        return $Parity * $Offset;
1534    }
1535
1536    /**
1537     * Localtime of the timestamp
1538     *
1539     * @param long              $time
1540     *
1541     * @access private
1542     * @return array
1543     */
1544    private function gmtime($time) {
1545        $TZOffset = $this->GetTZOffset($time);
1546
1547        $t_time = $time - $TZOffset * 60; #Counter adjust for localtime()
1548        $t_arr = localtime($t_time, 1);
1549
1550        return $t_arr;
1551    }
1552
1553    /**
1554     * Sets the properties in a MAPI object according to an Sync object and a property mapping
1555     *
1556     * @param mixed             $mapimessage
1557     * @param SyncObject        $message
1558     * @param array             $mapping
1559     *
1560     * @access private
1561     * @return
1562     */
1563    private function setPropsInMAPI($mapimessage, $message, $mapping) {
1564        $mapiprops = $this->getPropIdsFromStrings($mapping);
1565        $unsetVars = $message->getUnsetVars();
1566        $propsToDelete = array();
1567        $propsToSet = array();
1568
1569        foreach ($mapiprops as $asprop => $mapiprop) {
1570            if(isset($message->$asprop)) {
1571
1572                // UTF8->windows1252.. this is ok for all numerical values
1573                if(mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY) {
1574                    if(is_array($message->$asprop))
1575                        $value = array_map("u2wi", $message->$asprop);
1576                    else
1577                        $value = u2wi($message->$asprop);
1578                } else {
1579                    $value = $message->$asprop;
1580                }
1581
1582                // Make sure the php values are the correct type
1583                switch(mapi_prop_type($mapiprop)) {
1584                    case PT_BINARY:
1585                    case PT_STRING8:
1586                        settype($value, "string");
1587                        break;
1588                    case PT_BOOLEAN:
1589                        settype($value, "boolean");
1590                        break;
1591                    case PT_SYSTIME:
1592                    case PT_LONG:
1593                        settype($value, "integer");
1594                        break;
1595                }
1596
1597                // decode base64 value
1598                if($mapiprop == PR_RTF_COMPRESSED) {
1599                    $value = base64_decode($value);
1600                    if(strlen($value) == 0)
1601                        continue; // PDA will sometimes give us an empty RTF, which we'll ignore.
1602
1603                    // Note that you can still remove notes because when you remove notes it gives
1604                    // a valid compressed RTF with nothing in it.
1605
1606                }
1607                // if an "empty array" is to be saved, it the mvprop should be deleted - fixes Mantis #468
1608                if (is_array($value) && empty($value)) {
1609                    $propsToDelete[] = $mapiprop;
1610                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->setPropsInMAPI(): Property '%s' to be deleted as it is an empty array", $asprop));
1611                }
1612                else {
1613                    // all properties will be set at once
1614                    $propsToSet[$mapiprop] = $value;
1615                }
1616            }
1617            elseif (in_array($asprop, $unsetVars)) {
1618                $propsToDelete[] = $mapiprop;
1619            }
1620        }
1621
1622        mapi_setprops($mapimessage, $propsToSet);
1623        if (mapi_last_hresult()) {
1624            Zlog::Write(LOGLEVEL_WARN, sprintf("Failed to set properties, trying to set them separately. Error code was:%x", mapi_last_hresult()));
1625            $this->setPropsIndividually($mapimessage, $propsToSet, $mapiprops);
1626        }
1627
1628        mapi_deleteprops($mapimessage, $propsToDelete);
1629
1630        //clean up
1631        unset($unsetVars, $propsToDelete);
1632    }
1633
1634    /**
1635     * Sets the properties one by one in a MAPI object
1636     *
1637     * @param mixed             &$mapimessage
1638     * @param array             &$propsToSet
1639     * @param array             &$mapiprops
1640     *
1641     * @access private
1642     * @return
1643     */
1644    private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) {
1645        foreach ($propsToSet as $prop => $value) {
1646            mapi_setprops($mapimessage, array($prop => $value));
1647            if (mapi_last_hresult()) {
1648                Zlog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult()));
1649            }
1650        }
1651
1652    }
1653
1654    /**
1655     * Gets the properties from a MAPI object and sets them in the Sync object according to mapping
1656     *
1657     * @param SyncObject        &$message
1658     * @param mixed             $mapimessage
1659     * @param array             $mapping
1660     *
1661     * @access private
1662     * @return
1663     */
1664    private function getPropsFromMAPI(&$message, $mapimessage, $mapping) {
1665        $messageprops = $this->getProps($mapimessage, $mapping);
1666        foreach ($mapping as $asprop => $mapiprop) {
1667             // Get long strings via openproperty
1668            if (isset($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))])) {
1669                if ($messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT ||
1670                    $messageprops[mapi_prop_tag(PT_ERROR, mapi_prop_id($mapiprop))] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) {
1671                    $messageprops[$mapiprop] = MAPIUtils::readPropStream($mapimessage, $mapiprop);
1672                }
1673            }
1674
1675            if(isset($messageprops[$mapiprop])) {
1676                if(mapi_prop_type($mapiprop) == PT_BOOLEAN) {
1677                    // Force to actual '0' or '1'
1678                    if($messageprops[$mapiprop])
1679                        $message->$asprop = 1;
1680                    else
1681                        $message->$asprop = 0;
1682                } else {
1683                    // Special handling for PR_MESSAGE_FLAGS
1684                    if($mapiprop == PR_MESSAGE_FLAGS)
1685                        $message->$asprop = $messageprops[$mapiprop] & 1; // only look at 'read' flag
1686                    else if($mapiprop == PR_RTF_COMPRESSED)
1687                        //do not send rtf to the mobile
1688                        continue;
1689                    else if(is_array($messageprops[$mapiprop]))
1690                        $message->$asprop = array_map("w2u", $messageprops[$mapiprop]);
1691                    else {
1692                        if(mapi_prop_type($mapiprop) != PT_BINARY && mapi_prop_type($mapiprop) != PT_MV_BINARY)
1693                            $message->$asprop = w2u($messageprops[$mapiprop]);
1694                        else
1695                            $message->$asprop = $messageprops[$mapiprop];
1696                    }
1697                }
1698            }
1699        }
1700    }
1701
1702    /**
1703     * Wraps getPropIdsFromStrings() calls
1704     *
1705     * @param mixed             &$mapiprops
1706     *
1707     * @access private
1708     * @return
1709     */
1710    private function getPropIdsFromStrings(&$mapiprops) {
1711        return getPropIdsFromStrings($this->store, $mapiprops);
1712    }
1713
1714    /**
1715     * Wraps mapi_getprops() calls
1716     *
1717     * @param mixed             &$mapiprops
1718     *
1719     * @access private
1720     * @return
1721     */
1722    protected function getProps($mapimessage, &$mapiproperties) {
1723        $mapiproperties = $this->getPropIdsFromStrings($mapiproperties);
1724        return mapi_getprops($mapimessage, $mapiproperties);
1725    }
1726
1727    /**
1728     * Returns an GMT timezone array
1729     *
1730     * @access private
1731     * @return array
1732     */
1733    private function getGMTTZ() {
1734        $tz = array(
1735            "bias" => 0,
1736            "tzname" => "",
1737            "dstendyear" => 0,
1738            "dstendmonth" => 10,
1739            "dstendday" => 0,
1740            "dstendweek" => 5,
1741            "dstendhour" => 2,
1742            "dstendminute" => 0,
1743            "dstendsecond" => 0,
1744            "dstendmillis" => 0,
1745            "stdbias" => 0,
1746            "tznamedst" => "",
1747            "dststartyear" => 0,
1748            "dststartmonth" => 3,
1749            "dststartday" => 0,
1750            "dststartweek" => 5,
1751            "dststarthour" => 1,
1752            "dststartminute" => 0,
1753            "dststartsecond" => 0,
1754            "dststartmillis" => 0,
1755            "dstbias" => -60
1756    );
1757
1758        return $tz;
1759    }
1760
1761    /**
1762     * Unpack timezone info from MAPI
1763     *
1764     * @param string    $data
1765     *
1766     * @access private
1767     * @return array
1768     */
1769    private function getTZFromMAPIBlob($data) {
1770        $unpacked = unpack("lbias/lstdbias/ldstbias/" .
1771                           "vconst1/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
1772                           "vconst2/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis", $data);
1773        return $unpacked;
1774    }
1775
1776    /**
1777     * Unpack timezone info from Sync
1778     *
1779     * @param string    $data
1780     *
1781     * @access private
1782     * @return array
1783     */
1784    private function getTZFromSyncBlob($data) {
1785        $tz = unpack(   "lbias/a64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/" .
1786                        "lstdbias/a64tznamedst/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/" .
1787                        "ldstbias", $data);
1788
1789        // Make the structure compatible with class.recurrence.php
1790        $tz["timezone"] = $tz["bias"];
1791        $tz["timezonedst"] = $tz["dstbias"];
1792
1793        return $tz;
1794    }
1795
1796    /**
1797     * Pack timezone info for Sync
1798     *
1799     * @param array     $tz
1800     *
1801     * @access private
1802     * @return string
1803     */
1804    private function getSyncBlobFromTZ($tz) {
1805        // set the correct TZ name (done using the Bias)
1806        if (!isset($tz["tzname"]) || !$tz["tzname"] || !isset($tz["tznamedst"]) || !$tz["tznamedst"])
1807            $tz = TimezoneUtil::FillTZNames($tz);
1808
1809        $packed = pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
1810                $tz["bias"], $tz["tzname"], 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
1811                $tz["stdbias"], $tz["tznamedst"], 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"],
1812                $tz["dstbias"]);
1813
1814        return $packed;
1815    }
1816
1817    /**
1818     * Pack timezone info for MAPI
1819     *
1820     * @param array     $tz
1821     *
1822     * @access private
1823     * @return string
1824     */
1825    private function getMAPIBlobFromTZ($tz) {
1826        $packed = pack("lll" . "vvvvvvvvv" . "vvvvvvvvv",
1827                      $tz["bias"], $tz["stdbias"], $tz["dstbias"],
1828                      0, 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
1829                      0, 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"]);
1830
1831        return $packed;
1832    }
1833
1834    /**
1835     * Checks the date to see if it is in DST, and returns correct GMT date accordingly
1836     *
1837     * @param long      $localtime
1838     * @param array     $tz
1839     *
1840     * @access private
1841     * @return long
1842     */
1843    private function getGMTTimeByTZ($localtime, $tz) {
1844        if(!isset($tz) || !is_array($tz))
1845            return $localtime;
1846
1847        if($this->isDST($localtime, $tz))
1848            return $localtime + $tz["bias"]*60 + $tz["dstbias"]*60;
1849        else
1850            return $localtime + $tz["bias"]*60;
1851    }
1852
1853    /**
1854     * Returns the local time for the given GMT time, taking account of the given timezone
1855     *
1856     * @param long      $gmttime
1857     * @param array     $tz
1858     *
1859     * @access private
1860     * @return long
1861     */
1862    private function getLocaltimeByTZ($gmttime, $tz) {
1863        if(!isset($tz) || !is_array($tz))
1864            return $gmttime;
1865
1866        if($this->isDST($gmttime - $tz["bias"]*60, $tz)) // may bug around the switch time because it may have to be 'gmttime - bias - dstbias'
1867            return $gmttime - $tz["bias"]*60 - $tz["dstbias"]*60;
1868        else
1869            return $gmttime - $tz["bias"]*60;
1870    }
1871
1872    /**
1873     * Returns TRUE if it is the summer and therefore DST is in effect
1874     *
1875     * @param long      $localtime
1876     * @param array     $tz
1877     *
1878     * @access private
1879     * @return boolean
1880     */
1881    private function isDST($localtime, $tz) {
1882        if( !isset($tz) || !is_array($tz) ||
1883            !isset($tz["dstbias"]) || $tz["dstbias"] == 0 ||
1884            !isset($tz["dststartmonth"]) || $tz["dststartmonth"] == 0 ||
1885            !isset($tz["dstendmonth"]) || $tz["dstendmonth"] == 0)
1886            return false;
1887
1888        $year = gmdate("Y", $localtime);
1889        $start = $this->getTimestampOfWeek($year, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststartday"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"]);
1890        $end = $this->getTimestampOfWeek($year, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendday"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"]);
1891
1892        if($start < $end) {
1893            // northern hemisphere (july = dst)
1894          if($localtime >= $start && $localtime < $end)
1895              $dst = true;
1896          else
1897              $dst = false;
1898        } else {
1899            // southern hemisphere (january = dst)
1900          if($localtime >= $end && $localtime < $start)
1901              $dst = false;
1902          else
1903              $dst = true;
1904        }
1905
1906        return $dst;
1907    }
1908
1909    /**
1910     * Returns the local timestamp for the $week'th $wday of $month in $year at $hour:$minute:$second
1911     *
1912     * @param int       $year
1913     * @param int       $month
1914     * @param int       $week
1915     * @param int       $wday
1916     * @param int       $hour
1917     * @param int       $minute
1918     * @param int       $second
1919     *
1920     * @access private
1921     * @return long
1922     */
1923    private function getTimestampOfWeek($year, $month, $week, $wday, $hour, $minute, $second) {
1924        if ($month == 0)
1925            return;
1926
1927        $date = gmmktime($hour, $minute, $second, $month, 1, $year);
1928
1929        // Find first day in month which matches day of the week
1930        while(1) {
1931            $wdaynow = gmdate("w", $date);
1932            if($wdaynow == $wday)
1933                break;
1934            $date += 24 * 60 * 60;
1935        }
1936
1937        // Forward $week weeks (may 'overflow' into the next month)
1938        $date = $date + $week * (24 * 60 * 60 * 7);
1939
1940        // Reverse 'overflow'. Eg week '10' will always be the last week of the month in which the
1941        // specified weekday exists
1942        while(1) {
1943            $monthnow = gmdate("n", $date); // gmdate returns 1-12
1944            if($monthnow > $month)
1945                $date = $date - (24 * 7 * 60 * 60);
1946            else
1947                break;
1948        }
1949
1950        return $date;
1951    }
1952
1953    /**
1954     * Normalize the given timestamp to the start of the day
1955     *
1956     * @param long      $timestamp
1957     *
1958     * @access private
1959     * @return long
1960     */
1961    private function getDayStartOfTimestamp($timestamp) {
1962        return $timestamp - ($timestamp % (60 * 60 * 24));
1963    }
1964
1965    /**
1966     * Returns an SMTP address from an entry id
1967     *
1968     * @param string    $entryid
1969     *
1970     * @access private
1971     * @return string
1972     */
1973    private function getSMTPAddressFromEntryID($entryid) {
1974        $addrbook = $this->getAddressbook();
1975
1976        $mailuser = mapi_ab_openentry($addrbook, $entryid);
1977        if(!$mailuser)
1978            return "";
1979
1980        $props = mapi_getprops($mailuser, array(PR_ADDRTYPE, PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS));
1981
1982        $addrtype = isset($props[PR_ADDRTYPE]) ? $props[PR_ADDRTYPE] : "";
1983
1984        if(isset($props[PR_SMTP_ADDRESS]))
1985            return $props[PR_SMTP_ADDRESS];
1986
1987        if($addrtype == "SMTP" && isset($props[PR_EMAIL_ADDRESS]))
1988            return $props[PR_EMAIL_ADDRESS];
1989        elseif ($addrtype == "ZARAFA" && isset($props[PR_EMAIL_ADDRESS])) {
1990            $userinfo = mapi_zarafa_getuser_by_name($this->store, $props[PR_EMAIL_ADDRESS]);
1991            if (is_array($userinfo) && isset($userinfo["emailaddress"]))
1992                return $userinfo["emailaddress"];
1993        }
1994
1995        return "";
1996    }
1997
1998    /**
1999     * Builds a displayname from several separated values
2000     *
2001     * @param SyncContact       $contact
2002     *
2003     * @access private
2004     * @return string
2005     */
2006    private function composeDisplayName(&$contact) {
2007        // Set display name and subject to a combined value of firstname and lastname
2008        $cname = (isset($contact->prefix))?u2w($contact->prefix)." ":"";
2009        $cname .= u2w($contact->firstname);
2010        $cname .= (isset($contact->middlename))?" ". u2w($contact->middlename):"";
2011        $cname .= " ". u2w($contact->lastname);
2012        $cname .= (isset($contact->suffix))?" ". u2w($contact->suffix):"";
2013        return trim($cname);
2014    }
2015
2016    /**
2017     * Sets all dependent properties for an email address
2018     *
2019     * @param string            $emailAddress
2020     * @param string            $displayName
2021     * @param int               $cnt
2022     * @param array             &$props
2023     * @param array             &$properties
2024     * @param array             &$nremails
2025     * @param int               &$abprovidertype
2026     *
2027     * @access private
2028     * @return
2029     */
2030    private function setEmailAddress($emailAddress, $displayName, $cnt, &$props, &$properties, &$nremails, &$abprovidertype){
2031        if (isset($emailAddress)) {
2032            $name = (isset($displayName)) ? $displayName : $emailAddress;
2033
2034            $props[$properties["emailaddress$cnt"]] = $emailAddress;
2035            $props[$properties["emailaddressdemail$cnt"]] = $emailAddress;
2036            $props[$properties["emailaddressdname$cnt"]] = $name;
2037            $props[$properties["emailaddresstype$cnt"]] = "SMTP";
2038            $props[$properties["emailaddressentryid$cnt"]] = mapi_createoneoff($name, "SMTP", $emailAddress);
2039            $nremails[] = $cnt - 1;
2040            $abprovidertype |= 2 ^ ($cnt - 1);
2041        }
2042    }
2043
2044    /**
2045     * Sets the properties for an address string
2046     *
2047     * @param string            $type               which address is being set
2048     * @param string            $city
2049     * @param string            $country
2050     * @param string            $postalcode
2051     * @param string            $state
2052     * @param string            $street
2053     * @param array             &$props
2054     * @param array             &$properties
2055     *
2056     * @access private
2057     * @return
2058     */
2059     private function setAddress($type, &$city, &$country, &$postalcode, &$state, &$street, &$props, &$properties) {
2060        if (isset($city)) $props[$properties[$type."city"]] = $city = u2w($city);
2061
2062        if (isset($country)) $props[$properties[$type."country"]] = $country = u2w($country);
2063
2064        if (isset($postalcode)) $props[$properties[$type."postalcode"]] = $postalcode = u2w($postalcode);
2065
2066        if (isset($state)) $props[$properties[$type."state"]] = $state = u2w($state);
2067
2068        if (isset($street)) $props[$properties[$type."street"]] = $street = u2w($street);
2069
2070        //set composed address
2071        $address = Utils::BuildAddressString($street, $postalcode, $city, $state, $country);
2072        if ($address) $props[$properties[$type."address"]] = $address;
2073    }
2074
2075    /**
2076     * Sets the properties for a mailing address
2077     *
2078     * @param string            $city
2079     * @param string            $country
2080     * @param string            $postalcode
2081     * @param string            $state
2082     * @param string            $street
2083     * @param string            $address
2084     * @param array             &$props
2085     * @param array             &$properties
2086     *
2087     * @access private
2088     * @return
2089     */
2090    private function setMailingAddress($city, $country, $postalcode,  $state, $street, $address, &$props, &$properties) {
2091        if (isset($city)) $props[$properties["city"]] = $city;
2092        if (isset($country)) $props[$properties["country"]] = $country;
2093        if (isset($postalcode)) $props[$properties["postalcode"]] = $postalcode;
2094        if (isset($state)) $props[$properties["state"]] = $state;
2095        if (isset($street)) $props[$properties["street"]] = $street;
2096        if (isset($address)) $props[$properties["postaladdress"]] = $address;
2097    }
2098
2099    /**
2100     * Sets data in a recurrence array
2101     *
2102     * @param SyncObject        $message
2103     * @param array             &$recur
2104     *
2105     * @access private
2106     * @return
2107     */
2108    private function setRecurrence($message, &$recur) {
2109        if (isset($message->complete)) {
2110            $recur["complete"] = $message->complete;
2111        }
2112
2113        if(!isset($message->recurrence->interval))
2114            $message->recurrence->interval = 1;
2115
2116        //set the default value of numoccur
2117        $recur["numoccur"] = 0;
2118        //a place holder for recurrencetype property
2119        $recur["recurrencetype"] = 0;
2120
2121        switch($message->recurrence->type) {
2122            case 0:
2123                $recur["type"] = 10;
2124                if(isset($message->recurrence->dayofweek))
2125                    $recur["subtype"] = 1;
2126                else
2127                    $recur["subtype"] = 0;
2128
2129                $recur["everyn"] = $message->recurrence->interval * (60 * 24);
2130                $recur["recurrencetype"] = 1;
2131                break;
2132            case 1:
2133                $recur["type"] = 11;
2134                $recur["subtype"] = 1;
2135                $recur["everyn"] = $message->recurrence->interval;
2136                $recur["recurrencetype"] = 2;
2137                break;
2138            case 2:
2139                $recur["type"] = 12;
2140                $recur["subtype"] = 2;
2141                $recur["everyn"] = $message->recurrence->interval;
2142                $recur["recurrencetype"] = 3;
2143                break;
2144            case 3:
2145                $recur["type"] = 12;
2146                $recur["subtype"] = 3;
2147                $recur["everyn"] = $message->recurrence->interval;
2148                $recur["recurrencetype"] = 3;
2149                break;
2150            case 4:
2151                $recur["type"] = 13;
2152                $recur["subtype"] = 1;
2153                $recur["everyn"] = $message->recurrence->interval * 12;
2154                $recur["recurrencetype"] = 4;
2155                break;
2156            case 5:
2157                $recur["type"] = 13;
2158                $recur["subtype"] = 2;
2159                $recur["everyn"] = $message->recurrence->interval * 12;
2160                $recur["recurrencetype"] = 4;
2161                break;
2162            case 6:
2163                $recur["type"] = 13;
2164                $recur["subtype"] = 3;
2165                $recur["everyn"] = $message->recurrence->interval * 12;
2166                $recur["recurrencetype"] = 4;
2167                break;
2168        }
2169
2170        // "start" and "end" are in GMT when passing to class.recurrence
2171        $recur["end"] = $this->getDayStartOfTimestamp(0x7fffffff); // Maximum GMT value for end by default
2172
2173        if(isset($message->recurrence->until)) {
2174            $recur["term"] = 0x21;
2175            $recur["end"] = $message->recurrence->until;
2176        } else if(isset($message->recurrence->occurrences)) {
2177            $recur["term"] = 0x22;
2178            $recur["numoccur"] = $message->recurrence->occurrences;
2179        } else {
2180            $recur["term"] = 0x23;
2181        }
2182
2183        if(isset($message->recurrence->dayofweek))
2184            $recur["weekdays"] = $message->recurrence->dayofweek;
2185        if(isset($message->recurrence->weekofmonth))
2186            $recur["nday"] = $message->recurrence->weekofmonth;
2187        if(isset($message->recurrence->monthofyear)) {
2188            // MAPI stores months as the amount of minutes until the beginning of the month in a
2189            // non-leapyear. Why this is, is totally unclear.
2190            $monthminutes = array(0,44640,84960,129600,172800,217440,260640,305280,348480,393120,437760,480960);
2191            $recur["month"] = $monthminutes[$message->recurrence->monthofyear-1];
2192        }
2193        if(isset($message->recurrence->dayofmonth))
2194            $recur["monthday"] = $message->recurrence->dayofmonth;
2195    }
2196
2197    /**
2198     * Extracts the email address (mailbox@host) from an email address because
2199     * some devices send email address as "Firstname Lastname" <email@me.com>
2200     *
2201     *  @link http://developer.berlios.de/mantis/view.php?id=486
2202     *
2203     *  @param string           $email
2204     *
2205     *  @access private
2206     *  @return string or false on error
2207     */
2208    private function extractEmailAddress($email) {
2209        if (!isset($this->zRFC822)) $this->zRFC822 = new Mail_RFC822();
2210        $parsedAddress = $this->zRFC822->parseAddressList($email);
2211        if (!isset($parsedAddress[0]->mailbox) || !isset($parsedAddress[0]->host)) return false;
2212
2213        return $parsedAddress[0]->mailbox.'@'.$parsedAddress[0]->host;
2214    }
2215
2216    /**
2217     * Returns the message body for a required format
2218     *
2219     * @param MAPIMessage       $mapimessage
2220     * @param int               $bpReturnType
2221     * @param SyncObject        $message
2222     *
2223     * @access private
2224     * @return boolean
2225     */
2226    private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) {
2227        //default value is PR_BODY
2228        $property = PR_BODY;
2229        switch ($bpReturnType) {
2230            case SYNC_BODYPREFERENCE_HTML:
2231                $property = PR_HTML;
2232                break;
2233            case SYNC_BODYPREFERENCE_RTF:
2234                $property = PR_RTF_COMPRESSED;
2235                break;
2236            case SYNC_BODYPREFERENCE_MIME:
2237                $stat = $this->imtoinet($mapimessage, $message);
2238                if (isset($message->asbody))
2239                    $message->asbody->type = $bpReturnType;
2240                return $stat;
2241       }
2242
2243        $body = mapi_message_openproperty($mapimessage, $property);
2244        //set the properties according to supported AS version
2245        if (Request::GetProtocolVersion() >= 12.0) {
2246            $message->asbody = new SyncBaseBody();
2247            $message->asbody->type = $bpReturnType;
2248            if ($bpReturnType == SYNC_BODYPREFERENCE_RTF)
2249                $message->asbody->data = base64_encode($body);
2250            elseif (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML)
2251                $message->asbody->data = Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body);
2252            else
2253                $message->asbody->data = w2u($body);
2254            $message->asbody->estimatedDataSize = strlen($message->asbody->data);
2255        }
2256        else {
2257            $message->body = str_replace("\n","\r\n", w2u(str_replace("\r", "", $body)));
2258            $message->bodysize = strlen($message->body);
2259            $message->bodytruncated = 0;
2260        }
2261
2262        return true;
2263    }
2264
2265    /**
2266     * A wrapper for mapi_inetmapi_imtoinet function
2267     *
2268     * @param MAPIMessage       $mapimessage
2269     * @param SyncObject        $message
2270     *
2271     * @access private
2272     * @return boolean
2273     */
2274    private function imtoinet($mapimessage, &$message) {
2275        if (function_exists("mapi_inetmapi_imtoinet")) {
2276            $addrbook = $this->getAddressbook();
2277            $mstream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, array());
2278
2279            $mstreamstat = mapi_stream_stat($mstream);
2280            if ($mstreamstat['cb'] < MAX_EMBEDDED_SIZE) {
2281                if (Request::GetProtocolVersion() >= 12.0) {
2282                    if (!isset($message->asbody))
2283                        $message->asbody = new SyncBaseBody();
2284                    //TODO data should be wrapped in a MapiStreamWrapper
2285                    $message->asbody->data = mapi_stream_read($mstream, MAX_EMBEDDED_SIZE);
2286                    $message->asbody->estimatedDataSize = $mstreamstat["cb"];
2287                    $message->asbody->truncated = 0;
2288                }
2289                else {
2290                    $message->mimetruncated = 0;
2291                    //TODO mimedata should be a wrapped in a MapiStreamWrapper
2292                    $message->mimedata = mapi_stream_read($mstream, MAX_EMBEDDED_SIZE);
2293                    $message->mimesize = $mstreamstat["cb"];
2294                }
2295                unset($message->body, $message->bodytruncated);
2296                return true;
2297            }
2298            ZLog::Write(LOGLEVEL_WARN, sprintf("Your request (%d bytes) exceeds the value for inline attachments (%d bytes). You can change the value of MAX_EMBEDDED_SIZE in config.php", $mstreamstat['cb'], MAX_EMBEDDED_SIZE));
2299        }
2300        return false;
2301    }
2302
2303    /**
2304     * Sets the message body
2305     *
2306     * @param MAPIMessage       $mapimessage
2307     * @param ContentParameters $contentparameters
2308     * @param SyncObject        $message
2309     */
2310    private function setMessageBody($mapimessage, $contentparameters, &$message) {
2311        //get the available body preference types
2312        $bpTypes = $contentparameters->GetBodyPreference();
2313        if ($bpTypes !== false) {
2314            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BodyPreference types: %s", implode(', ', $bpTypes)));
2315            //do not send mime data if the client requests it
2316            if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) && ($key = array_search(SYNC_BODYPREFERENCE_MIME, $bpTypes)!== false)) {
2317                unset($bpTypes[$key]);
2318                ZLog::Write(LOGLEVEL_DEBUG, sprintf("Remove mime body preference type because the device required no mime support. BodyPreference types: %s", implode(', ', $bpTypes)));
2319            }
2320            //get the best fitting preference type
2321            $bpReturnType = Utils::GetBodyPreferenceBestMatch($bpTypes);
2322            ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetBodyPreferenceBestMatch: %d", $bpReturnType));
2323            $bpo = $contentparameters->BodyPreference($bpReturnType);
2324            ZLog::Write(LOGLEVEL_DEBUG, sprintf("bpo: truncation size:'%d', allornone:'%d', preview:'%d'", $bpo->GetTruncationSize(), $bpo->GetAllOrNone(), $bpo->GetPreview()));
2325
2326            $this->setMessageBodyForType($mapimessage, $bpReturnType, $message);
2327            //only set the truncation size data if device set it in request
2328            if (    $bpo->GetTruncationSize() != false &&
2329                    $bpReturnType != SYNC_BODYPREFERENCE_MIME &&
2330                    $message->asbody->estimatedDataSize > $bpo->GetTruncationSize() &&
2331                    $contentparameters->GetTruncation() != SYNC_TRUNCATION_ALL // do not truncate message if the whole is requested, e.g. on fetch
2332                ) {
2333                $message->asbody->data = Utils::Utf8_truncate($message->asbody->data, $bpo->GetTruncationSize());
2334                $message->asbody->truncated = 1;
2335
2336            }
2337            // set the preview or windows phones won't show the preview of an email
2338            if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) {
2339                $message->asbody->preview = Utils::Utf8_truncate(MAPIUtils::readPropStream($mapimessage, PR_BODY), $bpo->GetPreview());
2340            }
2341        }
2342        else {
2343            // Override 'body' for truncation
2344            $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
2345            $this->setMessageBodyForType($mapimessage, SYNC_BODYPREFERENCE_PLAIN, $message);
2346
2347            if($message->bodysize > $truncsize) {
2348                $message->body = Utils::Utf8_truncate($message->body, $truncsize);
2349                $message->bodytruncated = 1;
2350            }
2351
2352            if (!isset($message->body) || strlen($message->body) == 0)
2353                $message->body = " ";
2354
2355            if ($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_ALWAYS) {
2356                //set the html body for iphone in AS 2.5 version
2357                $this->imtoinet($mapimessage, $message);
2358            }
2359        }
2360    }
2361
2362    /**
2363     * Calculates the native body type of a message using available properties. Refer to oxbbody
2364     *
2365     * @param array             $messageprops
2366     *
2367     * @access private
2368     * @return int
2369     */
2370    private function getNativeBodyType($messageprops) {
2371        //check if the properties are set and get the error code if needed
2372        if (!isset($messageprops[PR_BODY]))             $messageprops[PR_BODY]              = $this->getError(PR_BODY, $messageprops);
2373        if (!isset($messageprops[PR_RTF_COMPRESSED]))   $messageprops[PR_RTF_COMPRESSED]    = $this->getError(PR_RTF_COMPRESSED, $messageprops);
2374        if (!isset($messageprops[PR_HTML]))             $messageprops[PR_HTML]              = $this->getError(PR_HTML, $messageprops);
2375        if (!isset($messageprops[PR_RTF_IN_SYNC]))      $messageprops[PR_RTF_IN_SYNC]       = $this->getError(PR_RTF_IN_SYNC, $messageprops);
2376
2377        if ( // 1
2378            ($messageprops[PR_BODY]             == MAPI_E_NOT_FOUND) &&
2379            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_FOUND) &&
2380            ($messageprops[PR_HTML]             == MAPI_E_NOT_FOUND))
2381            return SYNC_BODYPREFERENCE_PLAIN;
2382        elseif ( // 2
2383            ($messageprops[PR_BODY]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2384            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_FOUND) &&
2385            ($messageprops[PR_HTML]             == MAPI_E_NOT_FOUND))
2386            return SYNC_BODYPREFERENCE_PLAIN;
2387        elseif ( // 3
2388            ($messageprops[PR_BODY]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2389            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_ENOUGH_MEMORY) &&
2390            ($messageprops[PR_HTML]             == MAPI_E_NOT_FOUND))
2391            return SYNC_BODYPREFERENCE_RTF;
2392        elseif ( // 4
2393            ($messageprops[PR_BODY]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2394            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_ENOUGH_MEMORY) &&
2395            ($messageprops[PR_HTML]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2396            ($messageprops[PR_RTF_IN_SYNC]))
2397            return SYNC_BODYPREFERENCE_RTF;
2398        elseif ( // 5
2399            ($messageprops[PR_BODY]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2400            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_ENOUGH_MEMORY) &&
2401            ($messageprops[PR_HTML]             == MAPI_E_NOT_ENOUGH_MEMORY) &&
2402            (!$messageprops[PR_RTF_IN_SYNC]))
2403            return SYNC_BODYPREFERENCE_HTML;
2404        elseif ( // 6
2405            ($messageprops[PR_RTF_COMPRESSED]   != MAPI_E_NOT_FOUND   || $messageprops[PR_RTF_COMPRESSED]  == MAPI_E_NOT_ENOUGH_MEMORY) &&
2406            ($messageprops[PR_HTML]             != MAPI_E_NOT_FOUND   || $messageprops[PR_HTML]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2407            ($messageprops[PR_RTF_IN_SYNC]))
2408            return SYNC_BODYPREFERENCE_RTF;
2409        elseif ( // 7
2410            ($messageprops[PR_RTF_COMPRESSED]   != MAPI_E_NOT_FOUND   || $messageprops[PR_RTF_COMPRESSED]  == MAPI_E_NOT_ENOUGH_MEMORY) &&
2411            ($messageprops[PR_HTML]             != MAPI_E_NOT_FOUND   || $messageprops[PR_HTML]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2412            (!$messageprops[PR_RTF_IN_SYNC]))
2413            return SYNC_BODYPREFERENCE_HTML;
2414        elseif ( // 8
2415            ($messageprops[PR_BODY]             != MAPI_E_NOT_FOUND   || $messageprops[PR_BODY]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2416            ($messageprops[PR_RTF_COMPRESSED]   != MAPI_E_NOT_FOUND   || $messageprops[PR_RTF_COMPRESSED]  == MAPI_E_NOT_ENOUGH_MEMORY) &&
2417            ($messageprops[PR_RTF_IN_SYNC]))
2418            return SYNC_BODYPREFERENCE_RTF;
2419        elseif ( // 9.1
2420            ($messageprops[PR_BODY]             != MAPI_E_NOT_FOUND   || $messageprops[PR_BODY]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2421            ($messageprops[PR_RTF_COMPRESSED]   != MAPI_E_NOT_FOUND   || $messageprops[PR_RTF_COMPRESSED]  == MAPI_E_NOT_ENOUGH_MEMORY) &&
2422            (!$messageprops[PR_RTF_IN_SYNC]))
2423            return SYNC_BODYPREFERENCE_PLAIN;
2424        elseif ( // 9.2
2425            ($messageprops[PR_RTF_COMPRESSED]   != MAPI_E_NOT_FOUND   || $messageprops[PR_RTF_COMPRESSED]  == MAPI_E_NOT_ENOUGH_MEMORY) &&
2426            ($messageprops[PR_BODY]             == MAPI_E_NOT_FOUND) &&
2427            ($messageprops[PR_HTML]             == MAPI_E_NOT_FOUND))
2428            return SYNC_BODYPREFERENCE_RTF;
2429        elseif ( // 9.3
2430            ($messageprops[PR_BODY]             != MAPI_E_NOT_FOUND   || $messageprops[PR_BODY]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2431            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_FOUND) &&
2432            ($messageprops[PR_HTML]             == MAPI_E_NOT_FOUND))
2433            return SYNC_BODYPREFERENCE_PLAIN;
2434        elseif ( // 9.4
2435            ($messageprops[PR_HTML]             != MAPI_E_NOT_FOUND   || $messageprops[PR_HTML]            == MAPI_E_NOT_ENOUGH_MEMORY) &&
2436            ($messageprops[PR_BODY]             == MAPI_E_NOT_FOUND) &&
2437            ($messageprops[PR_RTF_COMPRESSED]   == MAPI_E_NOT_FOUND))
2438            return SYNC_BODYPREFERENCE_HTML;
2439        else // 10
2440            return SYNC_BODYPREFERENCE_PLAIN;
2441    }
2442
2443    /**
2444     * Returns the error code for a given property. Helper for getNativeBodyType function.
2445     *
2446     * @param int               $tag
2447     * @param array             $messageprops
2448     *
2449     * @access private
2450     * @return int (MAPI_ERROR_CODE)
2451     */
2452    private function getError($tag, $messageprops) {
2453        $prBodyError = mapi_prop_tag(PT_ERROR, mapi_prop_id($tag));
2454        if(isset($messageprops[$prBodyError]) && mapi_is_error($messageprops[$prBodyError])) {
2455            if($messageprops[$prBodyError] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT ||
2456                 $messageprops[$prBodyError] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) {
2457                    return MAPI_E_NOT_ENOUGH_MEMORY;
2458            }
2459        }
2460            return MAPI_E_NOT_FOUND;
2461    }
2462
2463    /**
2464    * Sets properties for an email message
2465    *
2466    * @param mixed             $mapimessage
2467    * @param SyncMail          $message
2468    *
2469    * @access private
2470    * @return void
2471    */
2472    private function setFlag($mapimessage, &$message){
2473        // do nothing if protocoll version is lower than 12.0 as flags haven't been defined before
2474        if (Request::GetProtocolVersion() < 12.0 ) return;
2475
2476        $message->flag = new SyncMailFlags();
2477
2478        $this->getPropsFromMAPI($message->flag, $mapimessage, MAPIMapping::GetMailFlagsMapping());
2479    }
2480
2481    /**
2482     * Sets information from SyncBaseBody type for a MAPI message.
2483     *
2484     * @param SyncBaseBody $asbody
2485     * @param array $props
2486     * @param array $appointmentprops
2487     *
2488     * @access private
2489     * @return void
2490     */
2491    private function setASbody($asbody, &$props, $appointmentprops) {
2492        if (isset($asbody->type) && isset($asbody->data) && strlen($asbody->data) > 0) {
2493            switch ($asbody->type) {
2494                case SYNC_BODYPREFERENCE_PLAIN:
2495                default:
2496                //set plain body if the type is not in valid range
2497                    $props[$appointmentprops["body"]] = u2w($asbody->data);
2498                    break;
2499                case SYNC_BODYPREFERENCE_HTML:
2500                    $props[$appointmentprops["html"]] = u2w($asbody->data);
2501                    break;
2502                case SYNC_BODYPREFERENCE_RTF:
2503                    break;
2504                case SYNC_BODYPREFERENCE_MIME:
2505                    break;
2506            }
2507        }
2508        else {
2509            ZLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->setASbody either type or data are not set. Setting to empty body");
2510            $props[$appointmentprops["body"]] = "";
2511        }
2512    }
2513
2514    /**
2515     * Get MAPI addressbook object
2516     *
2517     * @access private
2518     * @return MAPIAddressbook object to be used with mapi_ab_* or false on failure
2519     */
2520    private function getAddressbook() {
2521        if (isset($this->addressbook) && $this->addressbook) {
2522            return $this->addressbook;
2523        }
2524        $this->addressbook = mapi_openaddressbook($this->session);
2525        $result = mapi_last_hresult();
2526        if ($result && $this->addressbook === false) {
2527            ZLog::Write(LOGLEVEL_ERROR, sprintf("MAPIProvider->getAddressbook error opening addressbook 0x%X", $result));
2528            return false;
2529        }
2530        return $this->addressbook;
2531    }
2532}
2533
2534?>
Note: See TracBrowser for help on using the repository browser.