source: trunk/zpush/backend/zarafa/mapi/class.meetingrequest.php @ 7589

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

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

Line 
1<?php
2/*
3 * Copyright 2005 - 2012  Zarafa B.V.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License, version 3,
7 * as published by the Free Software Foundation with the following additional
8 * term according to sec. 7:
9 *
10 * According to sec. 7 of the GNU Affero General Public License, version
11 * 3, the terms of the AGPL are supplemented with the following terms:
12 *
13 * "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
14 * the Program under the AGPL does not imply a trademark license.
15 * Therefore any rights, title and interest in our trademarks remain
16 * entirely with us.
17 *
18 * However, if you propagate an unmodified version of the Program you are
19 * allowed to use the term "Zarafa" to indicate that you distribute the
20 * Program. Furthermore you may use our trademarks where it is necessary
21 * to indicate the intended purpose of a product or service provided you
22 * use it in accordance with honest practices in industrial or commercial
23 * matters.  If you want to propagate modified versions of the Program
24 * under the name "Zarafa" or "Zarafa Server", you may only do so if you
25 * have a written permission by Zarafa B.V. (to acquire a permission
26 * please contact Zarafa at trademark@zarafa.com).
27 *
28 * The interactive user interface of the software displays an attribution
29 * notice containing the term "Zarafa" and/or the logo of Zarafa.
30 * Interactive user interfaces of unmodified and modified versions must
31 * display Appropriate Legal Notices according to sec. 5 of the GNU
32 * Affero General Public License, version 3, when you propagate
33 * unmodified or modified versions of the Program. In accordance with
34 * sec. 7 b) of the GNU Affero General Public License, version 3, these
35 * Appropriate Legal Notices must retain the logo of Zarafa or display
36 * the words "Initial Development by Zarafa" if the display of the logo
37 * is not reasonably feasible for technical reasons. The use of the logo
38 * of Zarafa in Legal Notices is allowed for unmodified and modified
39 * versions of the software.
40 *
41 * This program is distributed in the hope that it will be useful,
42 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 * GNU Affero General Public License for more details.
45 *
46 * You should have received a copy of the GNU Affero General Public License
47 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
48 *
49 */
50
51class Meetingrequest {
52    /*
53     * NOTE
54     *
55     * This class is designed to modify and update meeting request properties
56     * and to search for linked appointments in the calendar. It does not
57     * - set standard properties like subject or location
58     * - commit property changes through savechanges() (except in accept() and decline())
59     *
60     * To set all the other properties, just handle the item as any other appointment
61     * item. You aren't even required to set those properties before or after using
62     * this class. If you update properties before REsending a meeting request (ie with
63     * a time change) you MUST first call updateMeetingRequest() so the internal counters
64     * can be updated. You can then submit the message any way you like.
65     *
66     */
67
68    /*
69     * How to use
70     * ----------
71     *
72     * Sending a meeting request:
73     * - Create appointment item as normal, but as 'tentative'
74     *   (this is the state of the item when the receiving user has received but
75     *    not accepted the item)
76     * - Set recipients as normally in e-mails
77     * - Create Meetingrequest class instance
78     * - Call setMeetingRequest(), this turns on all the meeting request properties in the
79     *   calendar item
80     * - Call sendMeetingRequest(), this sends a copy of the item with some extra properties
81     *
82     * Updating a meeting request:
83     * - Create Meetingrequest class instance
84     * - Call updateMeetingRequest(), this updates the counters
85     * - Call sendMeetingRequest()
86     *
87     * Clicking on a an e-mail:
88     * - Create Meetingrequest class instance
89     * - Check isMeetingRequest(), if true:
90     *   - Check isLocalOrganiser(), if true then ignore the message
91     *   - Check isInCalendar(), if not call doAccept(true, false, false). This adds the item in your
92     *     calendar as tentative without sending a response
93     *   - Show Accept, Tentative, Decline buttons
94     *   - When the user presses Accept, Tentative or Decline, call doAccept(false, true, true),
95     *     doAccept(true, true, true) or doDecline(true) respectively to really accept or decline and
96     *     send the response. This will remove the request from your inbox.
97     * - Check isMeetingRequestResponse, if true:
98     *   - Check isLocalOrganiser(), if not true then ignore the message
99     *   - Call processMeetingRequestResponse()
100     *     This will update the trackstatus of all recipients, and set the item to 'busy'
101     *     when all the recipients have accepted.
102     * - Check isMeetingCancellation(), if true:
103     *   - Check isLocalOrganiser(), if true then ignore the message
104     *   - Check isInCalendar(), if not, then ignore
105     *     Call processMeetingCancellation()
106     *   - Show 'Remove item' button to user
107     *   - When userpresses button, call doCancel(), which removes the item from your
108     *     calendar and deletes the message
109     */
110
111    // All properties for a recipient that are interesting
112    var $recipprops = Array(PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID, PR_OBJECT_TYPE, PR_SEARCH_KEY);
113
114    /**
115     * Indication whether the setting of resources in a Meeting Request is success (false) or if it
116     * has failed (integer).
117     */
118    var $errorSetResource;
119
120    /**
121     * Constructor
122     *
123     * Takes a store and a message. The message is an appointment item
124     * that should be converted into a meeting request or an incoming
125     * e-mail message that is a meeting request.
126     *
127     * The $session variable is optional, but required if the following features
128     * are to be used:
129     *
130     * - Sending meeting requests for meetings that are not in your own store
131     * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
132     */
133
134    function Meetingrequest($store, $message, $session = false, $enableDirectBooking = true)
135    {
136        $this->store = $store;
137        $this->message = $message;
138        $this->session = $session;
139        // This variable string saves time information for the MR.
140        $this->meetingTimeInfo = false;
141        $this->enableDirectBooking = $enableDirectBooking;
142
143        $properties["goid"] = "PT_BINARY:PSETID_Meeting:0x3";
144        $properties["goid2"] = "PT_BINARY:PSETID_Meeting:0x23";
145        $properties["type"] = "PT_STRING8:PSETID_Meeting:0x24";
146        $properties["meetingrecurring"] = "PT_BOOLEAN:PSETID_Meeting:0x5";
147        $properties["unknown2"] = "PT_BOOLEAN:PSETID_Meeting:0xa";
148        $properties["attendee_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1";
149        $properties["owner_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1a";
150        $properties["meetingstatus"] = "PT_LONG:PSETID_Appointment:0x8217";
151        $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
152        $properties["unknown6"] = "PT_LONG:PSETID_Meeting:0x4";
153        $properties["replytime"] = "PT_SYSTIME:PSETID_Appointment:0x8220";
154        $properties["usetnef"] = "PT_BOOLEAN:PSETID_Common:0x8582";
155        $properties["recurrence_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
156        $properties["reminderminutes"] = "PT_LONG:PSETID_Common:0x8501";
157        $properties["reminderset"] = "PT_BOOLEAN:PSETID_Common:0x8503";
158        $properties["sendasical"] = "PT_BOOLEAN:PSETID_Appointment:0x8200";
159        $properties["updatecounter"] = "PT_LONG:PSETID_Appointment:0x8201";                    // AppointmentSequenceNumber
160        $properties["last_updatecounter"] = "PT_LONG:PSETID_Appointment:0x8203";            // AppointmentLastSequence
161        $properties["unknown7"] = "PT_LONG:PSETID_Appointment:0x8202";
162        $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
163        $properties["intendedbusystatus"] = "PT_LONG:PSETID_Appointment:0x8224";
164        $properties["start"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
165        $properties["responselocation"] = "PT_STRING8:PSETID_Meeting:0x2";
166        $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
167        $properties["requestsent"] = "PT_BOOLEAN:PSETID_Appointment:0x8229";        // PidLidFInvited, MeetingRequestWasSent
168        $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
169        $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
170        $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
171        $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
172        $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
173        $properties["clipstart"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
174        $properties["clipend"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
175        $properties["start_recur_date"] = "PT_LONG:PSETID_Meeting:0xD";                // StartRecurTime
176        $properties["start_recur_time"] = "PT_LONG:PSETID_Meeting:0xE";                // StartRecurTime
177        $properties["end_recur_date"] = "PT_LONG:PSETID_Meeting:0xF";                // EndRecurDate
178        $properties["end_recur_time"] = "PT_LONG:PSETID_Meeting:0x10";                // EndRecurTime
179        $properties["is_exception"] = "PT_BOOLEAN:PSETID_Meeting:0xA";                // LID_IS_EXCEPTION
180        $properties["apptreplyname"] = "PT_STRING8:PSETID_Appointment:0x8230";
181        // Propose new time properties
182        $properties["proposed_start_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8250";
183        $properties["proposed_end_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8251";
184        $properties["proposed_duration"] = "PT_LONG:PSETID_Appointment:0x8256";
185        $properties["counter_proposal"] = "PT_BOOLEAN:PSETID_Appointment:0x8257";
186        $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
187        $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
188        $properties["meetingtype"] = "PT_LONG:PSETID_Meeting:0x26";
189        $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
190        $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
191        $properties["toattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823B";
192        $properties["ccattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823C";
193        $this->proptags = getPropIdsFromStrings($store, $properties);
194    }
195
196    /**
197     * Sets the direct booking property. This is an alternative to the setting of the direct booking
198     * property through the constructor. However, setting it in the constructor is prefered.
199     * @param Boolean $directBookingSetting
200     *
201     */
202    function setDirectBooking($directBookingSetting)
203    {
204        $this->enableDirectBooking = $directBookingSetting;
205    }
206
207    /**
208     * Returns TRUE if the message pointed to is an incoming meeting request and should
209     * therefore be replied to with doAccept or doDecline()
210     */
211    function isMeetingRequest()
212    {
213        $props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
214
215        if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request")
216            return true;
217    }
218
219    /**
220     * Returns TRUE if the message pointed to is a returning meeting request response
221     */
222    function isMeetingRequestResponse()
223    {
224        $props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
225
226        if(isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp") === 0)
227            return true;
228    }
229
230    /**
231     * Returns TRUE if the message pointed to is a cancellation request
232     */
233    function isMeetingCancellation()
234    {
235        $props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
236
237        if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Canceled")
238            return true;
239    }
240
241
242    /**
243     * Process an incoming meeting request response as Delegate. This will updates the appointment
244     * in Organiser's calendar.
245     * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
246     * of corresponding meeting in Calendar
247     */
248    function processMeetingRequestResponseAsDelegate()
249    {
250        if(!$this->isMeetingRequestResponse())
251            return;
252
253        $messageprops = mapi_getprops($this->message);
254
255        $goid2 = $messageprops[$this->proptags['goid2']];
256
257        if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
258            return;
259
260        // Find basedate in GlobalID(0x3), this can be a response for an occurrence
261        $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
262
263        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
264            $delegatorStore = $this->getDelegatorStore($messageprops);
265            $userStore = $delegatorStore['store'];
266            $calFolder = $delegatorStore['calFolder'];
267
268            if($calFolder){
269                $calendaritems = $this->findCalendarItems($goid2, $calFolder);
270
271                // $calendaritems now contains the ENTRYID's of all the calendar items to which
272                // this meeting request points.
273
274                // Open the calendar items, and update all the recipients of the calendar item that match
275                // the email address of the response.
276                if (!empty($calendaritems)) {
277                     return $this->processResponse($userStore, $calendaritems[0], $basedate, $messageprops);
278                }else{
279                    return false;
280                }
281            }
282        }
283    }
284
285
286    /**
287     * Process an incoming meeting request response. This updates the appointment
288     * in your calendar to show whether the user has accepted or declined.
289     * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
290     * of corresponding meeting in Calendar
291     */
292    function processMeetingRequestResponse()
293    {
294        if(!$this->isLocalOrganiser())
295            return;
296
297        if(!$this->isMeetingRequestResponse())
298            return;
299
300        // Get information we need from the response message
301        $messageprops = mapi_getprops($this->message, Array(
302                                                    $this->proptags['goid'],
303                                                    $this->proptags['goid2'],
304                                                    PR_OWNER_APPT_ID,
305                                                    PR_SENT_REPRESENTING_EMAIL_ADDRESS,
306                                                    PR_SENT_REPRESENTING_NAME,
307                                                    PR_SENT_REPRESENTING_ADDRTYPE,
308                                                    PR_SENT_REPRESENTING_ENTRYID,
309                                                    PR_MESSAGE_DELIVERY_TIME,
310                                                    PR_MESSAGE_CLASS,
311                                                    PR_PROCESSED,
312                                                    $this->proptags['proposed_start_whole'],
313                                                    $this->proptags['proposed_end_whole'],
314                                                    $this->proptags['proposed_duration'],
315                                                    $this->proptags['counter_proposal'],
316                                                    $this->proptags['attendee_critical_change']));
317
318        $goid2 = $messageprops[$this->proptags['goid2']];
319
320        if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
321            return;
322
323        // Find basedate in GlobalID(0x3), this can be a response for an occurrence
324        $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
325
326        $calendaritems = $this->findCalendarItems($goid2);
327
328        // $calendaritems now contains the ENTRYID's of all the calendar items to which
329        // this meeting request points.
330
331        // Open the calendar items, and update all the recipients of the calendar item that match
332        // the email address of the response.
333        if (!empty($calendaritems)) {
334            return $this->processResponse($this->store, $calendaritems[0], $basedate, $messageprops);
335        }else{
336            return false;
337        }
338    }
339
340    /**
341     * Process every incoming MeetingRequest response.This updates the appointment
342     * in your calendar to show whether the user has accepted or declined.
343     *@param resource $store contains the userStore in which the meeting is created
344     *@param $entryid contains the ENTRYID of the calendar items to which this meeting request points.
345     *@param boolean $basedate if present the create an exception
346     *@param array $messageprops contains m3/17/2010essage properties.
347     *@return entryids(storeid, parententryid, entryid, also basedate if response is occurrence) of corresponding meeting in Calendar
348     */
349    function processResponse($store, $entryid, $basedate, $messageprops)
350    {
351        $data = array();
352        $senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
353        $messageclass = $messageprops[PR_MESSAGE_CLASS];
354        $deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
355
356        // Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
357        // the email address of the response.
358        $calendaritem = mapi_msgstore_openentry($store, $entryid);
359        $calendaritemProps = mapi_getprops($calendaritem, array($this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']));
360
361        $data["storeid"] = bin2hex($calendaritemProps[PR_STORE_ENTRYID]);
362        $data["parententryid"] = bin2hex($calendaritemProps[PR_PARENT_ENTRYID]);
363        $data["entryid"] = bin2hex($calendaritemProps[PR_ENTRYID]);
364        $data["basedate"] = $basedate;
365        $data["updatecounter"] = isset($calendaritemProps[$this->proptags['updatecounter']]) ? $calendaritemProps[$this->proptags['updatecounter']] : 0;
366
367        /**
368         * Check if meeting is updated or not in organizer's calendar
369         */
370        $data["meeting_updated"] = $this->isMeetingUpdated();
371
372        if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
373            // meeting is already processed
374            return $data;
375        } else {
376            mapi_setprops($this->message, Array(PR_PROCESSED => true));
377            mapi_savechanges($this->message);
378        }
379
380        // if meeting is updated in organizer's calendar then we don't need to process
381        // old response
382        if($data['meeting_updated'] === true) {
383            return $data;
384        }
385
386        // If basedate is found, then create/modify exception msg and do processing
387        if ($basedate && $calendaritemProps[$this->proptags['recurring']]) {
388            $recurr = new Recurrence($store, $calendaritem);
389
390            // Copy properties from meeting request
391            $exception_props = mapi_getprops($this->message, array(PR_OWNER_APPT_ID,
392                                                $this->proptags['proposed_start_whole'],
393                                                $this->proptags['proposed_end_whole'],
394                                                $this->proptags['proposed_duration'],
395                                                $this->proptags['counter_proposal']
396                                            ));
397
398            // Create/modify exception
399            if($recurr->isException($basedate)) {
400                $recurr->modifyException($exception_props, $basedate);
401            } else {
402                // When we are creating an exception we need copy recipients from main recurring item
403                $recipTable =  mapi_message_getrecipienttable($calendaritem);
404                $recips = mapi_table_queryallrows($recipTable, $this->recipprops);
405
406                // Retrieve actual start/due dates from calendar item.
407                $exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
408                $exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
409
410                $recurr->createException($exception_props, $basedate, false, $recips);
411            }
412
413            mapi_message_savechanges($calendaritem);
414
415            $attach = $recurr->getExceptionAttachment($basedate);
416            if ($attach) {
417                $recurringItem = $calendaritem;
418                $calendaritem = mapi_attach_openobj($attach, MAPI_MODIFY);
419            } else {
420                return false;
421            }
422        }
423
424        // Get the recipients of the calendar item
425        $reciptable = mapi_message_getrecipienttable($calendaritem);
426        $recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
427
428        // FIXME we should look at the updatecounter property and compare it
429        // to the counter in the recipient to see if this update is actually
430        // newer than the status in the calendar item
431        $found = false;
432
433        $totalrecips = 0;
434        $acceptedrecips = 0;
435        foreach($recipients as $recipient) {
436            $totalrecips++;
437            if(isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID],$senderentryid)) {
438                $found = true;
439
440                /**
441                 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
442                 * on the corresponding recipientRow of meeting then we ignore this response mail.
443                 */
444                if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
445                    continue;
446                }
447
448                // The email address matches, update the row
449                $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
450                $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
451
452                // If this is a counter proposal, set the proposal properties in the recipient row
453                if(isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]){
454                    $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
455                    $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
456                    $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
457                }
458
459                mapi_message_modifyrecipients($calendaritem, MODRECIP_MODIFY, Array($recipient));
460            }
461            if(isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
462                $acceptedrecips++;
463        }
464
465        // If the recipient was not found in the original calendar item,
466        // then add the recpient as a new optional recipient
467        if(!$found) {
468            $recipient = Array();
469            $recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
470            $recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
471            $recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
472            $recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
473            $recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
474            $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
475            $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
476
477            // If this is a counter proposal, set the proposal properties in the recipient row
478            if(isset($messageprops[$this->proptags['counter_proposal']])){
479                $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
480                $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
481                $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
482            }
483
484            mapi_message_modifyrecipients($calendaritem, MODRECIP_ADD, Array($recipient));
485            $totalrecips++;
486            if($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
487                $acceptedrecips++;
488        }
489
490//TODO: Upate counter proposal number property on message
491/*
492If it is the first time this attendee has proposed a new date/time, increment the value of the PidLidAppointmentProposalNumber property on the organizerï¿œs meeting object, by 0x00000001. If this property did not previously exist on the organizerï¿œs meeting object, it MUST be set with a value of 0x00000001.
493*/
494        // If this is a counter proposal, set the counter proposal indicator boolean
495        if(isset($messageprops[$this->proptags['counter_proposal']])){
496            $props = Array();
497            if($messageprops[$this->proptags['counter_proposal']]){
498                $props[$this->proptags['counter_proposal']] = true;
499            }else{
500                $props[$this->proptags['counter_proposal']] = false;
501            }
502
503            mapi_message_setprops($calendaritem, $props);
504        }
505
506        mapi_message_savechanges($calendaritem);
507        if (isset($attach)) {
508            mapi_message_savechanges($attach);
509            mapi_message_savechanges($recurringItem);
510        }
511
512        return $data;
513    }
514
515
516    /**
517     * Process an incoming meeting request cancellation. This updates the
518     * appointment in your calendar to show that the meeting has been cancelled.
519     */
520    function processMeetingCancellation()
521    {
522        if($this->isLocalOrganiser())
523            return;
524
525        if(!$this->isMeetingCancellation())
526            return;
527
528        if(!$this->isInCalendar())
529            return;
530
531        $listProperties = $this->proptags;
532        $listProperties['subject'] = PR_SUBJECT;
533        $listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
534        $listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
535        $listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
536        $listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
537        $listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
538        $listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
539        $messageprops = mapi_getprops($this->message, $listProperties);
540        $store = $this->store;
541
542        $goid = $messageprops[$this->proptags['goid']];    //GlobalID (0x3)
543        if(!isset($goid))
544            return;
545
546        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
547            $delegatorStore = $this->getDelegatorStore($messageprops);
548            $store = $delegatorStore['store'];
549            $calFolder = $delegatorStore['calFolder'];
550        } else {
551            $calFolder = $this->openDefaultCalendar();
552        }
553
554        // First, find the items in the calendar by GOID
555        $calendaritems = $this->findCalendarItems($goid, $calFolder);
556        $basedate = $this->getBasedateFromGlobalID($goid);
557
558        if ($basedate) {
559            // Calendaritems with GlobalID were not found, so find main recurring item using CleanGlobalID(0x23)
560            if (empty($calendaritems)) {
561                // This meeting req is of an occurrance
562                $goid2 = $messageprops[$this->proptags['goid2']];
563
564                // First, find the items in the calendar by GOID
565                $calendaritems = $this->findCalendarItems($goid2);
566                foreach($calendaritems as $entryid) {
567                    // Open each calendar item and set the properties of the cancellation object
568                    $calendaritem = mapi_msgstore_openentry($store, $entryid);
569
570                    if ($calendaritem){
571                        $calendaritemProps = mapi_getprops($calendaritem, array($this->proptags['recurring']));
572                        if ($calendaritemProps[$this->proptags['recurring']]){
573                            $recurr = new Recurrence($store, $calendaritem);
574
575                            // Set message class
576                            $messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
577
578                            if($recurr->isException($basedate))
579                                $recurr->modifyException($messageprops, $basedate);
580                            else
581                                $recurr->createException($messageprops, $basedate);
582                        }
583                        mapi_savechanges($calendaritem);
584                    }
585                }
586            }
587        }
588
589        if (!isset($calendaritem)) {
590            foreach($calendaritems as $entryid) {
591                // Open each calendar item and set the properties of the cancellation object
592                $calendaritem = mapi_msgstore_openentry($store, $entryid);
593                mapi_message_setprops($calendaritem, $messageprops);
594                mapi_savechanges($calendaritem);
595            }
596        }
597    }
598
599    /**
600     * Returns true if the item is already in the calendar
601     */
602    function isInCalendar() {
603        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
604        $goid = $messageprops[$this->proptags['goid']];
605        if (isset($messageprops[$this->proptags['goid2']]))
606            $goid2 = $messageprops[$this->proptags['goid2']];
607
608        $basedate = $this->getBasedateFromGlobalID($goid);
609
610        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
611            $delegatorStore = $this->getDelegatorStore($messageprops);
612            $calFolder = $delegatorStore['calFolder'];
613        } else {
614            $calFolder = $this->openDefaultCalendar();
615        }
616        /**
617         * If basedate is found in globalID, then there are two possibilities.
618         * case 1) User has only this occurrence OR
619         * case 2) User has recurring item and has received an update for an occurrence
620         */
621        if ($basedate) {
622            // First try with GlobalID(0x3) (case 1)
623            $entryid = $this->findCalendarItems($goid, $calFolder);
624            // If not found then try with CleanGlobalID(0x23) (case 2)
625            if (!is_array($entryid) && isset($goid2))
626                $entryid = $this->findCalendarItems($goid2, $calFolder);
627        } else if (isset($goid2)) {
628            $entryid = $this->findCalendarItems($goid2, $calFolder);
629        }
630        else
631            return false;
632
633        return is_array($entryid);
634    }
635
636    /**
637     * Accepts the meeting request by moving the item to the calendar
638     * and sending a confirmation message back to the sender. If $tentative
639     * is TRUE, then the item is accepted tentatively. After accepting, you
640     * can't use this class instance any more. The message is closed. If you
641     * specify TRUE for 'move', then the item is actually moved (from your
642     * inbox probably) to the calendar. If you don't, it is copied into
643     * your calendar.
644     *@param boolean $tentative true if user as tentative accepted the meeting
645     *@param boolean $sendresponse true if a response has to be send to organizer
646     *@param boolean $move true if the meeting request should be moved to the deleted items after processing
647     *@param string $newProposedStartTime contains starttime if user has proposed other time
648     *@param string $newProposedEndTime contains endtime if user has proposed other time
649     *@param string $basedate start of day of occurrence for which user has accepted the recurrent meeting
650     *@return string $entryid entryid of item which created/updated in calendar
651     */
652    function doAccept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store=false, $basedate = false)
653    {
654        if($this->isLocalOrganiser())
655            return false;
656
657        // Remove any previous calendar items with this goid and appt id
658        $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_OWNER_APPT_ID, $this->proptags['updatecounter'], PR_PROCESSED, $this->proptags['recurring'], $this->proptags['intendedbusystatus'], PR_RCVD_REPRESENTING_NAME));
659
660        /**
661         *    if this function is called automatically with meeting request object then there will be
662         *    two possibilitites
663         *    1) meeting request is opened first time, in this case make a tentative appointment in
664                recipient's calendar
665         *    2) after this every subsequest request to open meeting request will not do any processing
666         */
667        if($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request" && $userAction == false) {
668            if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
669                // if meeting request is already processed then don't do anything
670                return false;
671            } else {
672                mapi_setprops($this->message, Array(PR_PROCESSED => true));
673                mapi_message_savechanges($this->message);
674            }
675        }
676
677        // If this meeting request is received by a delegate then open delegator's store.
678        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
679            $delegatorStore = $this->getDelegatorStore($messageprops);
680
681            $store = $delegatorStore['store'];
682            $calFolder = $delegatorStore['calFolder'];
683        } else {
684            $calFolder = $this->openDefaultCalendar();
685            $store = $this->store;
686        }
687
688        return $this->accept($tentative, $sendresponse, $move, $newProposedStartTime, $newProposedEndTime, $body, $userAction, $store, $calFolder, $basedate);
689    }
690
691    function accept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store, $calFolder, $basedate = false)
692    {
693        $messageprops = mapi_getprops($this->message);
694        $isDelegate = false;
695
696        if (isset($messageprops[PR_DELEGATED_BY_RULE]))
697            $isDelegate = true;
698
699        $goid = $messageprops[$this->proptags['goid2']];
700
701        // Retrieve basedate from globalID, if it is not recieved as argument
702        if (!$basedate)
703            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
704
705        if ($sendresponse)
706            $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $newProposedStartTime, $newProposedEndTime, $body, $store, $basedate, $calFolder);
707
708        $entryids = $this->findCalendarItems($goid, $calFolder);
709
710        if(is_array($entryids)) {
711            // Only check the first, there should only be one anyway...
712            $previtem = mapi_msgstore_openentry($store, $entryids[0]);
713            $prevcounterprops = mapi_getprops($previtem, array($this->proptags['updatecounter']));
714
715            // Check if the existing item has an updatecounter that is lower than the request we are processing. If not, then we ignore this call, since the
716            // meeting request is out of date.
717            /*
718                if(message_counter < appointment_counter) do_nothing
719                if(message_counter == appointment_counter) do_something_if_the_user_tells_us (userAction == true)
720                if(message_counter > appointment_counter) do_something_even_automatically
721            */
722            if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] < $prevcounterprops[$this->proptags['updatecounter']]) {
723                return false;
724            } else if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] == $prevcounterprops[$this->proptags['updatecounter']]) {
725                if($userAction == false && !$basedate) {
726                    return false;
727                }
728            }
729        }
730
731        // set counter proposal properties in calendar item when proposing new time
732        // @FIXME this can be moved before call to createResponse function so that function doesn't need to recalculate duration
733        $proposeNewTimeProps = array();
734        if($newProposedStartTime && $newProposedEndTime) {
735            $proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
736            $proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
737            $proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
738            $proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
739        }
740
741        /**
742         * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
743         * 1) If meeting req is of recurrence then we find all the occurrence in calendar because in past user might have recivied one or few occurrences.
744         * 2) If single occurrence then find occurrence itself using globalID and if item is not found then user cleanGlobalID to find main recurring item
745         * 3) Normal meeting req are handled normally has they were handled previously.
746         *
747         * Also user can respond(accept/decline) to item either from previewpane or from calendar by opening the item. If user is responding the meeting from previewpane
748         * and that item is not found in calendar then item is move else item is opened and all properties, attachments and recipient are copied from meeting request.
749         * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
750         */
751        if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
752            // While processing the item mark it as read.
753            mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
754
755            // This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
756            if ($messageprops[$this->proptags['recurring']] == true) {
757                $calendarItem = false;
758
759                // Find main recurring item based on GlobalID (0x3)
760                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
761                if (is_array($items)) {
762                    foreach($items as $key => $entryid)
763                        $calendarItem = mapi_msgstore_openentry($store, $entryid);
764                }
765
766                // Recurring item not found, so create new meeting in Calendar
767                if (!$calendarItem)
768                    $calendarItem = mapi_folder_createmessage($calFolder);
769
770                // Copy properties
771                $props = mapi_getprops($this->message);
772                $props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
773                $props[$this->proptags['meetingstatus']] = olMeetingReceived;
774                // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
775                $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
776
777                if (isset($props[$this->proptags['intendedbusystatus']])) {
778                    if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
779                        $props[$this->proptags['busystatus']] = $tentative;
780                    } else {
781                        $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
782                    }
783                    // we already have intendedbusystatus value in $props so no need to copy it
784                } else {
785                    $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
786                }
787
788                if($userAction) {
789                    // if user has responded then set replytime
790                    $props[$this->proptags['replytime']] = time();
791                }
792
793                mapi_setprops($calendarItem, $props);
794
795                // Copy attachments too
796                $this->replaceAttachments($this->message, $calendarItem);
797                // Copy recipients too
798                $this->replaceRecipients($this->message, $calendarItem, $isDelegate);
799
800                // Find all occurrences based on CleanGlobalID (0x23)
801                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
802                if (is_array($items)) {
803                    // Save all existing occurrence as exceptions
804                    foreach($items as $entryid) {
805                        // Open occurrence
806                        $occurrenceItem = mapi_msgstore_openentry($store, $entryid);
807
808                        // Save occurrence into main recurring item as exception
809                        if ($occurrenceItem) {
810                            $occurrenceItemProps = mapi_getprops($occurrenceItem, array($this->proptags['goid'], $this->proptags['recurring']));
811
812                            // Find basedate of occurrence item
813                            $basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
814                            if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true)
815                                $this->acceptException($calendarItem, $occurrenceItem, $basedate, true, $tentative, $userAction, $store, $isDelegate);
816                        }
817                    }
818                }
819                mapi_savechanges($calendarItem);
820                if ($move) {
821                    $wastebasket = $this->openDefaultWastebasket();
822                    mapi_folder_copymessages($calFolder, Array($props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
823                }
824                $entryid = $props[PR_ENTRYID];
825            } else {
826                /**
827                 * This meeting request is not recurring, so can be an exception or normal meeting.
828                 * If exception then find main recurring item and update exception
829                 * If main recurring item is not found then put exception into Calendar as normal meeting.
830                 */
831                $calendarItem = false;
832
833                // We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
834                if ($basedate) {
835                    // Find main recurring item from CleanGlobalID of this meeting request
836                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
837                    if (is_array($items)) {
838                        foreach($items as $key => $entryid) {
839                            $calendarItem = mapi_msgstore_openentry($store, $entryid);
840                        }
841                    }
842
843                    // Main recurring item is found, so now update exception
844                    if ($calendarItem) {
845                        $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
846                        $calendarItemProps = mapi_getprops($calendarItem, array(PR_ENTRYID));
847                        $entryid = $calendarItemProps[PR_ENTRYID];
848                    }
849                }
850
851                if (!$calendarItem) {
852                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
853
854                    if (is_array($items))
855                        mapi_folder_deletemessages($calFolder, $items);
856
857                    if ($move) {
858                        // All we have to do is open the default calendar,
859                        // set the mesage class correctly to be an appointment item
860                        // and move it to the calendar folder
861                        $sourcefolder = $this->openParentFolder();
862
863                        /* create a new calendar message, and copy the message to there,
864                           since we want to delete (move to wastebasket) the original message */
865                        $old_entryid = mapi_getprops($this->message, Array(PR_ENTRYID));
866                        $calmsg = mapi_folder_createmessage($calFolder);
867                        mapi_copyto($this->message, array(), array(), $calmsg); /* includes attachments and recipients */
868                        /* release old message */
869                        $message = null;
870
871                        $calItemProps = Array();
872                        $calItemProps[PR_MESSAGE_CLASS] = "IPM.Appointment";
873
874                        if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
875                            if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
876                                $calItemProps[$this->proptags['busystatus']] = $tentative;
877                            } else {
878                                $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
879                            }
880                            $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
881                        } else {
882                            $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
883                        }
884
885                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
886                        $calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
887                        if($userAction) {
888                            // if user has responded then set replytime
889                            $calItemProps[$this->proptags['replytime']] = time();
890                        }
891
892                        mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
893
894                        // get properties which stores owner information in meeting request mails
895                        $props = mapi_getprops($calmsg, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE));
896
897                        // add owner to recipient table
898                        $recips = array();
899                        $this->addOrganizer($props, $recips);
900
901                        if($isDelegate) {
902                            /**
903                             * If user is delegate then remove that user from recipienttable of the MR.
904                             * and delegate MR mail doesn't contain any of the attendees in recipient table.
905                             * So, other required and optional attendees are added from
906                             * toattendeesstring and ccattendeesstring properties.
907                             */
908                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
909                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
910                            mapi_message_modifyrecipients($calmsg, 0, $recips);
911                        } else {
912                            mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
913                        }
914
915                        mapi_message_savechanges($calmsg);
916
917                        // Move the message to the wastebasket
918                        $wastebasket = $this->openDefaultWastebasket();
919                        mapi_folder_copymessages($sourcefolder, array($old_entryid[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
920
921                        $messageprops = mapi_getprops($calmsg, array(PR_ENTRYID));
922                        $entryid = $messageprops[PR_ENTRYID];
923                    } else {
924                        // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
925                        $new = mapi_folder_createmessage($calFolder);
926                        $props = mapi_getprops($this->message);
927
928                        $props[PR_MESSAGE_CLASS] = "IPM.Appointment";
929                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
930                        $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
931
932                        if (isset($props[$this->proptags['intendedbusystatus']])) {
933                            if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
934                                $props[$this->proptags['busystatus']] = $tentative;
935                            } else {
936                                $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
937                            }
938                            // we already have intendedbusystatus value in $props so no need to copy it
939                        } else {
940                            $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
941                        }
942
943                        if($userAction) {
944                            // if user has responded then set replytime
945                            $props[$this->proptags['replytime']] = time();
946                        }
947
948                        mapi_setprops($new, $proposeNewTimeProps + $props);
949
950                        $reciptable = mapi_message_getrecipienttable($this->message);
951
952                        $recips = array();
953                        if(!$isDelegate)
954                            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
955
956                        $this->addOrganizer($props, $recips);
957
958                        if($isDelegate) {
959                            /**
960                             * If user is delegate then remove that user from recipienttable of the MR.
961                             * and delegate MR mail doesn't contain any of the attendees in recipient table.
962                             * So, other required and optional attendees are added from
963                             * toattendeesstring and ccattendeesstring properties.
964                             */
965                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
966                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
967                            mapi_message_modifyrecipients($new, 0, $recips);
968                        } else {
969                            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
970                        }
971                        mapi_message_savechanges($new);
972
973                        $props = mapi_getprops($new, array(PR_ENTRYID));
974                        $entryid = $props[PR_ENTRYID];
975                    }
976                }
977            }
978        } else {
979            // Here only properties are set on calendaritem, because user is responding from calendar.
980            $props = array();
981            $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
982
983            if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
984                if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
985                    $props[$this->proptags['busystatus']] = $tentative;
986                } else {
987                    $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
988                }
989                $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
990            } else {
991                $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
992            }
993
994            $props[$this->proptags['meetingstatus']] = olMeetingReceived;
995            $props[$this->proptags['replytime']] = time();
996
997            if ($basedate) {
998                $recurr = new Recurrence($store, $this->message);
999
1000                // Copy recipients list
1001                $reciptable = mapi_message_getrecipienttable($this->message);
1002                $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1003
1004                if($recurr->isException($basedate)) {
1005                    $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1006                } else {
1007                    $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1008                    $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1009
1010                    $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1011                    $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1012                    $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1013                    $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1014
1015                    $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1016                }
1017            } else {
1018                mapi_setprops($this->message, $proposeNewTimeProps + $props);
1019            }
1020            mapi_savechanges($this->message);
1021
1022            $entryid = $messageprops[PR_ENTRYID];
1023        }
1024
1025        return $entryid;
1026    }
1027
1028    /**
1029     * Declines the meeting request by moving the item to the deleted
1030     * items folder and sending a decline message. After declining, you
1031     * can't use this class instance any more. The message is closed.
1032     * When an occurrence is decline then false is returned because that
1033     * occurrence is deleted not the recurring item.
1034     *
1035     *@param boolean $sendresponse true if a response has to be sent to organizer
1036     *@param resource $store MAPI_store of user
1037     *@param string $basedate if specified contains starttime of day of an occurrence
1038     *@return boolean true if item is deleted from Calendar else false
1039     */
1040    function doDecline($sendresponse, $store=false, $basedate = false, $body = false)
1041    {
1042        $result = true;
1043        $calendaritem = false;
1044        if($this->isLocalOrganiser())
1045            return;
1046
1047        // Remove any previous calendar items with this goid and appt id
1048        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
1049
1050        // If this meeting request is received by a delegate then open delegator's store.
1051        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1052            $delegatorStore = $this->getDelegatorStore($messageprops);
1053
1054            $store = $delegatorStore['store'];
1055            $calFolder = $delegatorStore['calFolder'];
1056        } else {
1057            $calFolder = $this->openDefaultCalendar();
1058            $store = $this->store;
1059        }
1060
1061        $goid = $messageprops[$this->proptags['goid']];
1062
1063        // First, find the items in the calendar by GlobalObjid (0x3)
1064        $entryids = $this->findCalendarItems($goid, $calFolder);
1065
1066        if (!$basedate)
1067            $basedate = $this->getBasedateFromGlobalID($goid);
1068
1069        if($sendresponse)
1070            $this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
1071
1072        if ($basedate) {
1073            // use CleanGlobalObjid (0x23)
1074            $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1075
1076            foreach($calendaritems as $entryid) {
1077                // Open each calendar item and set the properties of the cancellation object
1078                $calendaritem = mapi_msgstore_openentry($store, $entryid);
1079
1080                // Recurring item is found, now delete exception
1081                if ($calendaritem)
1082                    $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1083            }
1084
1085            if ($this->isMeetingRequest())
1086                $calendaritem = false;
1087            else
1088                $result = false;
1089        }
1090
1091        if (!$calendaritem) {
1092            $calendar = $this->openDefaultCalendar();
1093
1094            if(!empty($entryids)) {
1095                mapi_folder_deletemessages($calendar, $entryids);
1096            }
1097
1098            // All we have to do to decline, is to move the item to the waste basket
1099            $wastebasket = $this->openDefaultWastebasket();
1100            $sourcefolder = $this->openParentFolder();
1101
1102            $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
1103
1104            // Release the message
1105            $this->message = null;
1106
1107            // Move the message to the waste basket
1108            mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1109        }
1110        return $result;
1111    }
1112
1113    /**
1114     * Removes a meeting request from the calendar when the user presses the
1115     * 'remove from calendar' button in response to a meeting cancellation.
1116     * @param string $basedate if specified contains starttime of day of an occurrence
1117     */
1118    function doRemoveFromCalendar($basedate)
1119    {
1120        if($this->isLocalOrganiser())
1121            return false;
1122
1123        $store = $this->store;
1124        $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_NAME, PR_MESSAGE_CLASS));
1125        $goid = $messageprops[$this->proptags['goid']];
1126
1127        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1128            $delegatorStore = $this->getDelegatorStore($messageprops);
1129            $store = $delegatorStore['store'];
1130            $calFolder = $delegatorStore['calFolder'];
1131        } else {
1132            $calFolder = $this->openDefaultCalendar();
1133        }
1134
1135        $wastebasket = $this->openDefaultWastebasket();
1136        $sourcefolder = $this->openParentFolder();
1137
1138        // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1139        if (strpos($messageprops[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') === 0) {
1140            /**
1141             * 'Remove from calendar' option from previewpane then we have to check GlobalID of this meeting request.
1142             * If basedate found then open meeting from calendar and delete that occurence.
1143             */
1144            $basedate = false;
1145            if ($goid) {
1146                // Retrieve GlobalID and find basedate in it.
1147                $basedate = $this->getBasedateFromGlobalID($goid);
1148
1149                // Basedate found, Now find item.
1150                if ($basedate) {
1151                    $guid = $this->setBasedateInGlobalID($goid);
1152
1153                    // First, find the items in the calendar by GOID
1154                    $calendaritems = $this->findCalendarItems($guid, $calFolder);
1155                    if(is_array($calendaritems)) {
1156                        foreach($calendaritems as $entryid) {
1157                            // Open each calendar item and set the properties of the cancellation object
1158                            $calendaritem = mapi_msgstore_openentry($store, $entryid);
1159
1160                            if ($calendaritem){
1161                                $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1162                            }
1163                        }
1164                    }
1165                }
1166            }
1167
1168            // It is normal/recurring meeting item.
1169            if (!$basedate) {
1170                if (!isset($calFolder)) $calFolder = $this->openDefaultCalendar();
1171
1172                $entryids = $this->findCalendarItems($goid, $calFolder);
1173
1174                if(is_array($entryids)){
1175                    // Move the calendaritem to the waste basket
1176                    mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
1177                }
1178            }
1179
1180            // Release the message
1181            $this->message = null;
1182
1183            // Move the message to the waste basket
1184            mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1185
1186        } else {
1187            // Here only properties are set on calendaritem, because user is responding from calendar.
1188            if ($basedate) { //remove the occurence
1189                $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1190            } else { //remove normal/recurring meeting item.
1191                // Move the message to the waste basket
1192                mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1193            }
1194        }
1195    }
1196
1197    /**
1198     * Removes the meeting request by moving the item to the deleted
1199     * items folder. After canceling, youcan't use this class instance
1200     * any more. The message is closed.
1201     */
1202    function doCancel()
1203    {
1204        if($this->isLocalOrganiser())
1205            return;
1206        if(!$this->isMeetingCancellation())
1207            return;
1208
1209        // Remove any previous calendar items with this goid and appt id
1210        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
1211        $goid = $messageprops[$this->proptags['goid']];
1212
1213        $entryids = $this->findCalendarItems($goid);
1214        $calendar = $this->openDefaultCalendar();
1215
1216        mapi_folder_deletemessages($calendar, $entryids);
1217
1218        // All we have to do to decline, is to move the item to the waste basket
1219
1220        $wastebasket = $this->openDefaultWastebasket();
1221        $sourcefolder = $this->openParentFolder();
1222
1223        $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
1224
1225        // Release the message
1226        $this->message = null;
1227
1228        // Move the message to the waste basket
1229        mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
1230    }
1231
1232
1233    /**
1234     * Sets the properties in the message so that is can be sent
1235     * as a meeting request. The caller has to submit the message. This
1236     * is only used for new MeetingRequests. Pass the appointment item as $message
1237     * in the constructor to do this.
1238     */
1239    function setMeetingRequest($basedate = false)
1240    {
1241        $props = mapi_getprops($this->message, Array($this->proptags['updatecounter']));
1242
1243        // Create a new global id for this item
1244        $goid = pack("H*", "040000008200E00074C5B7101A82E00800000000");
1245        for ($i=0; $i<36; $i++)
1246            $goid .= chr(rand(0, 255));
1247
1248        // Create a new appointment id for this item
1249        $apptid = rand();
1250
1251        $props[PR_OWNER_APPT_ID] = $apptid;
1252        $props[PR_ICON_INDEX] = 1026;
1253        $props[$this->proptags['goid']] = $goid;
1254        $props[$this->proptags['goid2']] = $goid;
1255
1256        if (!isset($props[$this->proptags['updatecounter']])) {
1257            $props[$this->proptags['updatecounter']] = 0;            // OL also starts sequence no with zero.
1258            $props[$this->proptags['last_updatecounter']] = 0;
1259        }
1260
1261        mapi_setprops($this->message, $props);
1262    }
1263
1264    /**
1265     * Sends a meeting request by copying it to the outbox, converting
1266     * the message class, adding some properties that are required only
1267     * for sending the message and submitting the message. Set cancel to
1268     * true if you wish to completely cancel the meeting request. You can
1269     * specify an optional 'prefix' to prefix the sent message, which is normally
1270     * 'Canceled: '
1271     */
1272    function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false)
1273    {
1274        $this->includesResources = false;
1275        $this->nonAcceptingResources = Array();
1276
1277        // Get the properties of the message
1278        $messageprops = mapi_getprops($this->message, Array($this->proptags['recurring']));
1279
1280        /*****************************************************************************************
1281         * Submit message to non-resource recipients
1282         */
1283        // Set BusyStatus to olTentative (1)
1284        // Set MeetingStatus to olMeetingReceived
1285        // Set ResponseStatus to olResponseNotResponded
1286
1287        /**
1288         * While sending recurrence meeting exceptions are not send as attachments
1289         * because first all exceptions are send and then recurrence meeting is sent.
1290         */
1291        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1292            // Book resource
1293            $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1294
1295            if (!$this->errorSetResource) {
1296                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1297
1298                // First send meetingrequest for recurring item
1299                $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
1300
1301                // Then send all meeting request for all exceptions
1302                $exceptions = $recurr->getAllExceptions();
1303                if ($exceptions) {
1304                    foreach($exceptions as $exceptionBasedate) {
1305                        $attach = $recurr->getExceptionAttachment($exceptionBasedate);
1306
1307                        if ($attach) {
1308                            $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1309                            $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
1310                            mapi_savechanges($attach);
1311                        }
1312                    }
1313                }
1314            }
1315        } else {
1316            // Basedate found, an exception is to be send
1317            if ($basedate) {
1318                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1319
1320                if ($cancel) {
1321                    //@TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1322                    $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1323                } else {
1324                    $attach = $recurr->getExceptionAttachment($basedate);
1325
1326                    if ($attach) {
1327                        $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1328
1329                        // Book resource for this occurrence
1330                        $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1331
1332                        if (!$this->errorSetResource) {
1333                            // Save all previous changes
1334                            mapi_savechanges($this->message);
1335
1336                            $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
1337                            mapi_savechanges($occurrenceItem);
1338                            mapi_savechanges($attach);
1339                        }
1340                    }
1341                }
1342            } else {
1343                // This is normal meeting
1344                $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1345
1346                if (!$this->errorSetResource) {
1347                    $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
1348                }
1349            }
1350        }
1351
1352        if(isset($this->errorSetResource) && $this->errorSetResource){
1353            return Array(
1354                'error' => $this->errorSetResource,
1355                'displayname' => $this->recipientDisplayname
1356            );
1357        }else{
1358            return true;
1359        }
1360    }
1361
1362
1363    function getFreeBusyInfo($entryID,$start,$end)
1364    {
1365        $result = array();
1366        $fbsupport = mapi_freebusysupport_open($this->session);
1367
1368        if(mapi_last_hresult() != NOERROR) {
1369            if(function_exists("dump")) {
1370                dump("Error in opening freebusysupport object.");
1371            }
1372            return $result;
1373        }
1374
1375        $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, array($entryID));
1376
1377        if($fbDataArray[0] != NULL){
1378            foreach($fbDataArray as $fbDataUser){
1379                $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1380                if($rangeuser1 == NULL){
1381                    return $result;
1382                }
1383
1384                $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1385                mapi_freebusyenumblock_reset($enumblock);
1386
1387                while(true){
1388                    $blocks = mapi_freebusyenumblock_next($enumblock, 100);
1389                    if(!$blocks){
1390                        break;
1391                    }
1392                    foreach($blocks as $blockItem){
1393                        $result[] = $blockItem;
1394                    }
1395                }
1396            }
1397        }
1398
1399        mapi_freebusysupport_close($fbsupport);
1400        return $result;
1401    }
1402
1403    /**
1404     * Updates the message after an update has been performed (for example,
1405     * changing the time of the meeting). This must be called before re-sending
1406     * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1407     * as it will automatically call setMeetingRequest on this object if it is the first
1408     * call to this function.
1409     */
1410    function updateMeetingRequest($basedate = false)
1411    {
1412        $messageprops = mapi_getprops($this->message, Array($this->proptags['last_updatecounter'], $this->proptags['goid']));
1413
1414        if(!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
1415            $this->setMeetingRequest($basedate);
1416        } else {
1417            $counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1418
1419            // increment value of last_updatecounter, last_updatecounter will be common for recurring series
1420            // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1421            // this way we can make sure that everytime we will be using a uniwue number for every operation
1422            mapi_setprops($this->message, Array($this->proptags['last_updatecounter'] => $counter));
1423        }
1424    }
1425
1426    /**
1427     * Returns TRUE if we are the organiser of the meeting.
1428     */
1429    function isLocalOrganiser()
1430    {
1431        if($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
1432            $messageid = $this->getAppointmentEntryID();
1433
1434            if(!isset($messageid))
1435                return false;
1436
1437            $message = mapi_msgstore_openentry($this->store, $messageid);
1438
1439            $messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
1440            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
1441            if ($basedate) {
1442                $recurr = new Recurrence($this->store, $message);
1443                $attach = $recurr->getExceptionAttachment($basedate);
1444                if ($attach) {
1445                    $occurItem = mapi_attach_openobj($attach);
1446                    $occurItemProps = mapi_getprops($occurItem, Array($this->proptags['responsestatus']));
1447                }
1448            }
1449
1450            $messageprops = mapi_getprops($message, Array($this->proptags['responsestatus']));
1451        }
1452
1453        /**
1454         * User can send recurring meeting or any occurrences from a recurring appointment so
1455         * to be organizer 'responseStatus' property should be 'olResponseOrganized' on either
1456         * of the recurring item or occurrence item.
1457         */
1458        if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized)
1459            || (isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized))
1460            return true;
1461        else
1462            return false;
1463    }
1464
1465    /**
1466     * Returns the entryid of the appointment that this message points at. This is
1467     * only used on messages that are not in the calendar.
1468     */
1469    function getAppointmentEntryID()
1470    {
1471        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid2']));
1472
1473        $goid2 = $messageprops[$this->proptags['goid2']];
1474
1475        $items = $this->findCalendarItems($goid2);
1476
1477        if(empty($items))
1478            return;
1479
1480        // There should be just one item. If there are more, we just take the first one
1481        return $items[0];
1482    }
1483
1484    /***************************************************************************************************
1485     * Support functions - INTERNAL ONLY
1486     ***************************************************************************************************
1487     */
1488
1489    /**
1490     * Return the tracking status of a recipient based on the IPM class (passed)
1491     */
1492    function getTrackStatus($class) {
1493        $status = olRecipientTrackStatusNone;
1494        switch($class)
1495        {
1496            case "IPM.Schedule.Meeting.Resp.Pos":
1497                $status = olRecipientTrackStatusAccepted;
1498                break;
1499
1500            case "IPM.Schedule.Meeting.Resp.Tent":
1501                $status = olRecipientTrackStatusTentative;
1502                break;
1503
1504            case "IPM.Schedule.Meeting.Resp.Neg":
1505                $status = olRecipientTrackStatusDeclined;
1506                break;
1507        }
1508        return $status;
1509    }
1510
1511    function openParentFolder() {
1512        $messageprops = mapi_getprops($this->message, Array(PR_PARENT_ENTRYID));
1513
1514        $parentfolder = mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1515        return $parentfolder;
1516    }
1517
1518    function openDefaultCalendar() {
1519        return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
1520    }
1521
1522    function openDefaultOutbox($store=false) {
1523        return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1524    }
1525
1526    function openDefaultWastebasket() {
1527        return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
1528    }
1529
1530    function getDefaultWastebasketEntryID() {
1531        return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
1532    }
1533
1534    function getDefaultSentmailEntryID($store=false) {
1535        return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1536    }
1537
1538    function getDefaultFolderEntryID($prop) {
1539        try {
1540            $inbox = mapi_msgstore_getreceivefolder($this->store);
1541        } catch (MAPIException $e) {
1542            // public store doesn't support this method
1543            if($e->getCode() == MAPI_E_NO_SUPPORT) {
1544                // don't propogate this error to parent handlers, if store doesn't support it
1545                $e->setHandled();
1546                return;
1547            }
1548        }
1549
1550        $inboxprops = mapi_getprops($inbox, Array($prop));
1551        if(!isset($inboxprops[$prop]))
1552            return;
1553
1554        return $inboxprops[$prop];
1555    }
1556
1557    function openDefaultFolder($prop) {
1558        $entryid = $this->getDefaultFolderEntryID($prop);
1559        $folder = mapi_msgstore_openentry($this->store, $entryid);
1560
1561        return $folder;
1562    }
1563
1564    function getBaseEntryID($prop, $store=false) {
1565        $storeprops = mapi_getprops( (($store)?$store:$this->store) , Array($prop));
1566        if(!isset($storeprops[$prop]))
1567            return;
1568
1569        return $storeprops[$prop];
1570    }
1571
1572    function openBaseFolder($prop, $store=false) {
1573        $entryid = $this->getBaseEntryID($prop, $store);
1574        $folder = mapi_msgstore_openentry( (($store)?$store:$this->store) , $entryid);
1575
1576        return $folder;
1577    }
1578    /**
1579     * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1580     *@param integer $status response status of attendee
1581     *@param integer $proposalStartTime proposed starttime by attendee
1582     *@param integer $proposalEndTime proposed endtime by attendee
1583     *@param integer $basedate date of occurrence which attendee has responded
1584     */
1585    function createResponse($status, $proposalStartTime=false, $proposalEndTime=false, $body=false, $store, $basedate = false, $calFolder) {
1586        $messageprops = mapi_getprops($this->message, Array(PR_SENT_REPRESENTING_ENTRYID,
1587                                                            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1588                                                            PR_SENT_REPRESENTING_ADDRTYPE,
1589                                                            PR_SENT_REPRESENTING_NAME,
1590                                                            $this->proptags['goid'],
1591                                                            $this->proptags['goid2'],
1592                                                            $this->proptags['location'],
1593                                                            $this->proptags['startdate'],
1594                                                            $this->proptags['duedate'],
1595                                                            $this->proptags['recurring'],
1596                                                            $this->proptags['recurring_pattern'],
1597                                                            $this->proptags['recurrence_data'],
1598                                                            $this->proptags['timezone_data'],
1599                                                            $this->proptags['timezone'],
1600                                                            $this->proptags['updatecounter'],
1601                                                            PR_SUBJECT,
1602                                                            PR_MESSAGE_CLASS,
1603                                                            PR_OWNER_APPT_ID,
1604                                                            $this->proptags['is_exception']
1605                                    ));
1606
1607        if ($basedate && $messageprops[PR_MESSAGE_CLASS] != "IPM.Schedule.Meeting.Request" ){
1608            // we are creating response from a recurring calendar item object
1609            // We found basedate,so opened occurrence and get properties.
1610            $recurr = new Recurrence($store, $this->message);
1611            $exception = $recurr->getExceptionAttachment($basedate);
1612
1613            if ($exception) {
1614                // Exception found, Now retrieve properties
1615                $imessage = mapi_attach_openobj($exception, 0);
1616                $imsgprops = mapi_getprops($imessage);
1617
1618                // If location is provided, copy it to the response
1619                if (isset($imsgprops[$this->proptags['location']])) {
1620                    $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1621                }
1622
1623                // Update $messageprops with timings of occurrence
1624                $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1625                $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1626
1627                // Meeting related properties
1628                $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1629                $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1630                $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1631            } else {
1632                // Exceptions is deleted.
1633                // Update $messageprops with timings of occurrence
1634                $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1635                $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1636
1637                $props[$this->proptags['meetingstatus']] = olNonMeeting;
1638                $props[$this->proptags['responsestatus']] = olResponseNone;
1639            }
1640
1641            $props[$this->proptags['recurring']] = false;
1642            $props[$this->proptags['is_exception']] = true;
1643        } else {
1644            // we are creating a response from meeting request mail (it could be recurring or non-recurring)
1645            // Send all recurrence info in response, if this is a recurrence meeting.
1646            $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
1647            $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
1648            if ($isRecurring || $isException) {
1649                if($isRecurring) {
1650                    $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
1651                }
1652                if($isException) {
1653                    $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
1654                }
1655                $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1656
1657                $calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
1658                $recurr = new Recurrence($store, $calendaritem);
1659            }
1660        }
1661
1662        // we are sending a response for recurring meeting request (or exception), so set some required properties
1663        if(isset($recurr) && $recurr) {
1664            if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
1665                $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
1666            }
1667
1668            if(!empty($messageprops[$this->proptags['recurrence_data']])) {
1669                $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
1670            }
1671
1672            $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
1673            $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
1674
1675            $this->generateRecurDates($recurr, $messageprops, $props);
1676        }
1677
1678        // Create a response message
1679        $recip = Array();
1680        $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1681        $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1682        $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1683        $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1684        $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1685
1686        switch($status) {
1687            case olResponseAccepted:
1688                $classpostfix = "Pos";
1689                $subjectprefix = _("Accepted");
1690                break;
1691            case olResponseDeclined:
1692                $classpostfix = "Neg";
1693                $subjectprefix = _("Declined");
1694                break;
1695            case olResponseTentative:
1696                $classpostfix = "Tent";
1697                $subjectprefix = _("Tentatively accepted");
1698                break;
1699        }
1700
1701        if($proposalStartTime && $proposalEndTime){
1702            // if attendee has proposed new time then change subject prefix
1703            $subjectprefix = _("New Time Proposed");
1704        }
1705
1706        $props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
1707
1708        $props[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Resp." . $classpostfix;
1709        if(isset($messageprops[PR_OWNER_APPT_ID]))
1710            $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
1711
1712        // Set GLOBALID AND CLEANGLOBALID, if exception then also set basedate into GLOBALID(0x3).
1713        $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
1714        $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
1715        $props[$this->proptags['updatecounter']] = $messageprops[$this->proptags['updatecounter']];
1716
1717        // get the default store, in which we have to store the accepted email by delegate or normal user.
1718        $defaultStore = $this->openDefaultStore();
1719        $props[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($defaultStore);
1720
1721        if($proposalStartTime && $proposalEndTime){
1722            $props[$this->proptags['proposed_start_whole']] = $proposalStartTime;
1723            $props[$this->proptags['proposed_end_whole']] = $proposalEndTime;
1724            $props[$this->proptags['proposed_duration']] = round($proposalEndTime - $proposalStartTime)/60;
1725            $props[$this->proptags['counter_proposal']] = true;
1726        }
1727
1728        //Set body message in Appointment
1729        if(isset($body)) {
1730            $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
1731        }
1732
1733        // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
1734        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
1735        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
1736
1737        // Set startdate and duedate in response mail.
1738        $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
1739        $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
1740
1741        // responselocation is used in the UI in Outlook on the response message
1742        if (isset($messageprops[$this->proptags['location']])) {
1743            $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
1744            $props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
1745        }
1746
1747        // check if $store is set and it is not equal to $defaultStore (means its the delegation case)
1748        if(isset($store) && isset($defaultStore)) {
1749            $storeProps = mapi_getprops($store, array(PR_ENTRYID));
1750            $defaultStoreProps = mapi_getprops($defaultStore, array(PR_ENTRYID));
1751
1752            if($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]){
1753                // get the properties of the other user (for which the logged in user is a delegate).
1754                $storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
1755                $addrbook = mapi_openaddressbook($this->session);
1756                $addrbookitem = mapi_ab_openentry($addrbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
1757                $addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
1758
1759                // setting the following properties will ensure that the delegation part of message.
1760                $props[PR_SENT_REPRESENTING_ENTRYID] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1761                $props[PR_SENT_REPRESENTING_NAME] = $addrbookitemprops[PR_DISPLAY_NAME];
1762                $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $addrbookitemprops[PR_EMAIL_ADDRESS];
1763                $props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
1764
1765                // get the properties of default store and set it accordingly
1766                $defaultStoreProps = mapi_getprops($defaultStore, array(PR_MAILBOX_OWNER_ENTRYID));
1767                $addrbookitem = mapi_ab_openentry($addrbook, $defaultStoreProps[PR_MAILBOX_OWNER_ENTRYID]);
1768                $addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
1769
1770                // set the following properties will ensure the sender's details, which will be the default user in this case.
1771                //the function returns array($name, $emailaddr, $addrtype, $entryid, $searchkey);
1772                $defaultUserDetails = $this->getOwnerAddress($defaultStore);
1773                $props[PR_SENDER_ENTRYID] = $defaultUserDetails[3];
1774                $props[PR_SENDER_EMAIL_ADDRESS] = $defaultUserDetails[1];
1775                $props[PR_SENDER_NAME] = $defaultUserDetails[0];
1776                $props[PR_SENDER_ADDRTYPE] = $defaultUserDetails[2];
1777            }
1778        }
1779
1780        // pass the default store to get the required store.
1781        $outbox = $this->openDefaultOutbox($defaultStore);
1782
1783        $message = mapi_folder_createmessage($outbox);
1784        mapi_setprops($message, $props);
1785        mapi_message_modifyrecipients($message, MODRECIP_ADD, Array($recip));
1786        mapi_message_savechanges($message);
1787        mapi_message_submitmessage($message);
1788    }
1789
1790    /**
1791     * Function which finds items in calendar based on specified parameters.
1792     *@param binary $goid GlobalID(0x3) of item
1793     *@param resource $calendar MAPI_folder of user
1794     *@param boolean $use_cleanGlobalID if true then search should be performed on cleanGlobalID(0x23) else globalID(0x3)
1795     */
1796    function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
1797        if(!$calendar) {
1798            // Open the Calendar
1799            $calendar = $this->openDefaultCalendar();
1800        }
1801
1802        // Find the item by restricting all items to the correct ID
1803        $restrict = Array(RES_AND, Array());
1804
1805        array_push($restrict[1], Array(RES_PROPERTY,
1806                                                    Array(RELOP => RELOP_EQ,
1807                                                          ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
1808                                                          VALUE => $goid
1809                                                    )
1810                                    ));
1811
1812        $calendarcontents = mapi_folder_getcontentstable($calendar);
1813
1814        $rows = mapi_table_queryallrows($calendarcontents, Array(PR_ENTRYID), $restrict);
1815
1816        if(empty($rows))
1817            return;
1818
1819        $calendaritems = Array();
1820
1821        // In principle, there should only be one row, but we'll handle them all just in case
1822        foreach($rows as $row) {
1823            $calendaritems[] = $row[PR_ENTRYID];
1824        }
1825
1826        return $calendaritems;
1827    }
1828
1829    // Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
1830    // same SMTP address when converted to SMTP
1831    function compareABEntryIDs($entryid1, $entryid2) {
1832        // If the session was not passed, just do a 'normal' compare.
1833        if(!$this->session)
1834            return $entryid1 == $entryid2;
1835
1836        $smtp1 = $this->getSMTPAddress($entryid1);
1837        $smtp2 = $this->getSMTPAddress($entryid2);
1838
1839        if($smtp1 == $smtp2)
1840            return true;
1841        else
1842            return false;
1843    }
1844
1845    // Gets the SMTP address of the passed addressbook entryid
1846    function getSMTPAddress($entryid) {
1847        if(!$this->session)
1848            return false;
1849
1850        $ab = mapi_openaddressbook($this->session);
1851
1852        $abitem = mapi_ab_openentry($ab, $entryid);
1853
1854        if(!$abitem)
1855            return "";
1856
1857        $props = mapi_getprops($abitem, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
1858
1859        if($props[PR_ADDRTYPE] == "SMTP") {
1860            return $props[PR_EMAIL_ADDRESS];
1861        }
1862        else return $props[PR_SMTP_ADDRESS];
1863    }
1864
1865    /**
1866     * Gets the properties associated with the owner of the passed store:
1867     * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY
1868     *
1869     * @param $store message store
1870     * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
1871     * not used when passed store is public store. for public store we are always returning logged in user's info.
1872     * @return properties of logged in user in an array in sequence of display_name, email address, address tyep,
1873     * entryid and search key.
1874     */
1875    function getOwnerAddress($store, $fallbackToLoggedInUser = true)
1876    {
1877        if(!$this->session)
1878            return false;
1879
1880        $storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID));
1881
1882        $ownerEntryId = false;
1883        if(isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
1884            $ownerEntryId = $storeProps[PR_USER_ENTRYID];
1885        }
1886
1887        if(isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
1888            $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1889        }
1890
1891        if($ownerEntryId) {
1892            $ab = mapi_openaddressbook($this->session);
1893
1894            $zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
1895            if(!$zarafaUser)
1896                return false;
1897
1898            $ownerProps = mapi_getprops($zarafaUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
1899
1900            $addrType = $ownerProps[PR_ADDRTYPE];
1901            $name = $ownerProps[PR_DISPLAY_NAME];
1902            $emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
1903            $searchKey = strtoupper($addrType) . ":" . strtoupper($emailAddr);
1904            $entryId = $ownerEntryId;
1905
1906            return array($name, $emailAddr, $addrType, $entryId, $searchKey);
1907        }
1908
1909        return false;
1910    }
1911
1912    // Opens this session's default message store
1913    function openDefaultStore()
1914    {
1915        $storestable = mapi_getmsgstorestable($this->session);
1916        $rows = mapi_table_queryallrows($storestable, array(PR_ENTRYID, PR_DEFAULT_STORE));
1917        $entry = false;
1918
1919        foreach($rows as $row) {
1920            if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
1921                $entryid = $row[PR_ENTRYID];
1922                break;
1923            }
1924        }
1925
1926        if(!$entryid)
1927            return false;
1928
1929        return mapi_openmsgstore($this->session, $entryid);
1930    }
1931    /**
1932     *  Function which adds organizer to recipient list which is passed.
1933     *  This function also checks if it has organizer.
1934     *
1935     * @param array $messageProps message properties
1936     * @param array $recipients    recipients list of message.
1937     * @param boolean $isException true if we are processing recipient of exception
1938     */
1939    function addOrganizer($messageProps, &$recipients, $isException = false){
1940
1941        $hasOrganizer = false;
1942        // Check if meeting already has an organizer.
1943        foreach ($recipients as $key => $recipient){
1944            if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1945                $hasOrganizer = true;
1946            } else if ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])){
1947                // Recipients for an occurrence
1948                $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1949            }
1950        }
1951
1952        if (!$hasOrganizer){
1953            // Create organizer.
1954            $organizer = array();
1955            $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1956            $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1957            $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1958            $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
1959            $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1960            $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1961            $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1962            $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1963
1964            // Add organizer to recipients list.
1965            array_unshift($recipients, $organizer);
1966        }
1967    }
1968
1969    /**
1970     * Function adds recipients in recips array from the string.
1971     *
1972     * @param array $recips recipient array.
1973     * @param string $recipString recipient string attendees.
1974     * @param int $type type of the recipient, MAPI_TO/MAPI_CC.
1975     */
1976    function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO)
1977    {
1978        $extraRecipient = array();
1979        $recipArray = explode(";", $recipString);
1980
1981        foreach($recipArray as $recip) {
1982            $recip = trim($recip);
1983            if (!empty($recip)) {
1984                $extraRecipient[PR_RECIPIENT_TYPE] = $recipType;
1985                $extraRecipient[PR_DISPLAY_NAME] = $recip;
1986                array_push($recips, $extraRecipient);
1987            }
1988        }
1989
1990    }
1991
1992    /**
1993     * Function which removes an exception/occurrence from recurrencing meeting
1994     * when a meeting cancellation of an occurrence is processed.
1995     *@param string $basedate basedate of an occurrence
1996     *@param resource $message recurring item from which occurrence has to be deleted
1997     *@param resource $store MAPI_MSG_Store which contains the item
1998     */
1999    function doRemoveExceptionFromCalendar($basedate, $message, $store)
2000    {
2001        $recurr = new Recurrence($store, $message);
2002        $recurr->createException(array(), $basedate, true);
2003        mapi_savechanges($message);
2004    }
2005
2006    /**
2007     * Function which returns basedate of an changed occurrance from globalID of meeting request.
2008     *@param binary $goid globalID
2009     *@return boolean true if basedate is found else false it not found
2010     */
2011    function getBasedateFromGlobalID($goid)
2012    {
2013        $hexguid = bin2hex($goid);
2014        $hexbase = substr($hexguid, 32, 8);
2015        $day = hexdec(substr($hexbase, 6, 2));
2016        $month = hexdec(substr($hexbase, 4, 2));
2017        $year = hexdec(substr($hexbase, 0, 4));
2018
2019        if ($day && $month && $year)
2020            return gmmktime(0, 0, 0, $month, $day, $year);
2021        else
2022            return false;
2023    }
2024
2025    /**
2026     * Function which sets basedate in globalID of changed occurrance which is to be send.
2027     *@param binary $goid globalID
2028     *@param string basedate of changed occurrance
2029     *@return binary globalID with basedate in it
2030     */
2031    function setBasedateInGlobalID($goid, $basedate = false)
2032    {
2033        $hexguid = bin2hex($goid);
2034        $year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
2035        $month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
2036        $day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
2037
2038        return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2039    }
2040    /**
2041     * Function which replaces attachments with copy_from in copy_to.
2042     *@param resource $copy_from MAPI_message from which attachments are to be copied.
2043     *@param resource $copy_to MAPI_message to which attachment are to be copied.
2044     *@param boolean $copyExceptions if true then all exceptions should also be sent as attachments
2045     */
2046    function replaceAttachments($copy_from, $copy_to, $copyExceptions = true)
2047    {
2048        /* remove all old attachments */
2049        $attachmentTable = mapi_message_getattachmenttable($copy_to);
2050        if($attachmentTable) {
2051            $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
2052
2053            foreach($attachments as $attach_props){
2054                /* remove exceptions too? */
2055                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
2056                    continue;
2057                mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
2058            }
2059        }
2060        $attachmentTable = false;
2061
2062        /* copy new attachments */
2063        $attachmentTable = mapi_message_getattachmenttable($copy_from);
2064        if($attachmentTable) {
2065            $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
2066
2067            foreach($attachments as $attach_props){
2068                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
2069                    continue;
2070
2071                $attach_old = mapi_message_openattach($copy_from, (int) $attach_props[PR_ATTACH_NUM]);
2072                $attach_newResourceMsg = mapi_message_createattach($copy_to);
2073                mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
2074                mapi_savechanges($attach_newResourceMsg);
2075            }
2076        }
2077    }
2078    /**
2079     * Function which replaces recipients in copy_to with recipients from copy_from.
2080     *@param resource $copy_from MAPI_message from which recipients are to be copied.
2081     *@param resource $copy_to MAPI_message to which recipients are to be copied.
2082     */
2083    function replaceRecipients($copy_from, $copy_to, $isDelegate = false)
2084    {
2085        $recipienttable = mapi_message_getrecipienttable($copy_from);
2086
2087        // If delegate, then do not add the delegate in recipients
2088        if ($isDelegate) {
2089            $delegate = mapi_getprops($copy_from, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
2090            $res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
2091            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $res);
2092        } else {
2093            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2094        }
2095
2096        $copy_to_recipientTable = mapi_message_getrecipienttable($copy_to);
2097        $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
2098        foreach($copy_to_recipientRows as $recipient) {
2099            mapi_message_modifyrecipients($copy_to, MODRECIP_REMOVE, array($recipient));
2100        }
2101
2102        mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
2103    }
2104    /**
2105     * Function creates meeting item in resource's calendar.
2106     *@param resource $message MAPI_message which is to create in resource's calendar
2107     *@param boolean $cancel cancel meeting
2108     *@param string $prefix prefix for subject of meeting
2109     */
2110    function bookResources($message, $cancel, $prefix, $basedate = false)
2111    {
2112        if(!$this->enableDirectBooking)
2113            return array();
2114
2115        // Get the properties of the message
2116        $messageprops = mapi_getprops($message);
2117
2118        if ($basedate) {
2119            $recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID));
2120
2121            $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2122            $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2123
2124            // Delete properties which are not needed.
2125            $deleteProps = array($this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD);
2126            foreach ($deleteProps as $propID) {
2127                if (isset($messageprops[$propID])) {
2128                    unset($messageprops[$propID]);
2129                }
2130            }
2131
2132            if (isset($messageprops[$this->proptags['recurring']])) $messageprops[$this->proptags['recurring']] = false;
2133
2134            // Set Outlook properties
2135            $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2136            $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2137            $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2138            $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2139            $messageprops[$this->proptags['attendee_critical_change']] = time();
2140            $messageprops[$this->proptags['owner_critical_change']] = time();
2141        }
2142
2143        // Get resource recipients
2144        $getResourcesRestriction = Array(RES_AND,
2145            Array(Array(RES_PROPERTY,
2146                Array(RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2147                    ULPROPTAG => PR_RECIPIENT_TYPE,
2148                    VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
2149                )
2150            ))
2151        );
2152        $recipienttable = mapi_message_getrecipienttable($message);
2153        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2154
2155        $this->errorSetResource = false;
2156        $resourceRecipData = Array();
2157
2158        // Put appointment into store resource users
2159        $i = 0;
2160        $len = count($resourceRecipients);
2161        while(!$this->errorSetResource && $i < $len){
2162            $request = array(array(PR_DISPLAY_NAME => $resourceRecipients[$i][PR_DISPLAY_NAME]));
2163            $ab = mapi_openaddressbook($this->session);
2164            $ret = mapi_ab_resolvename($ab, $request, EMS_AB_ADDRESS_LOOKUP);
2165            $result = mapi_last_hresult();
2166            if ($result == NOERROR){
2167                $result = $ret[0][PR_ENTRYID];
2168            }
2169            $resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
2170            $resourceABEntryID = $ret[0][PR_ENTRYID];
2171
2172            // Get StoreEntryID by username
2173            $user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
2174
2175            // Open store of the user
2176            $userStore = mapi_openmsgstore($this->session, $user_entryid);
2177            // Open root folder
2178            $userRoot = mapi_msgstore_openentry($userStore, null);
2179            // Get calendar entryID
2180            $userRootProps = mapi_getprops($userRoot, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
2181
2182            // Open Calendar folder   [check hresult==0]
2183            $accessToFolder = false;
2184            try {
2185                $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2186                if($calFolder){
2187                    $calFolderProps = mapi_getProps($calFolder, Array(PR_ACCESS));
2188                    if(($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0){
2189                        $accessToFolder = true;
2190                    }
2191                }
2192            } catch (MAPIException $e) {
2193                $e->setHandled();
2194                $this->errorSetResource = 1; // No access
2195            }
2196
2197            if($accessToFolder) {
2198                /**
2199                 * Get the LocalFreebusy message that contains the properties that
2200                 * are set to accept or decline resource meeting requests
2201                 */
2202                // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
2203                $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
2204                if($localFreebusyMsg){
2205                    $props = mapi_getprops($localFreebusyMsg, array(PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS));
2206
2207                    $acceptMeetingRequests = ($props[PR_PROCESS_MEETING_REQUESTS])?1:0;
2208                    $declineRecurringMeetingRequests = ($props[PR_DECLINE_RECURRING_MEETING_REQUESTS])?1:0;
2209                    $declineConflictingMeetingRequests = ($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS])?1:0;
2210                    if(!$acceptMeetingRequests){
2211                        /**
2212                         * When a resource has not been set to automatically accept meeting requests,
2213                         * the meeting request has to be sent to him rather than being put directly into
2214                         * his calendar. No error should be returned.
2215                         */
2216                        //$errorSetResource = 2;
2217                        $this->nonAcceptingResources[] = $resourceRecipients[$i];
2218                    }else{
2219                        if($declineRecurringMeetingRequests && !$cancel){
2220                            // Check if appointment is recurring
2221                            if($messageprops[ $this->proptags['recurring'] ]){
2222                                $this->errorSetResource = 3;
2223                            }
2224                        }
2225                        if($declineConflictingMeetingRequests && !$cancel){
2226                            // Check for conflicting items
2227                            $conflicting = false;
2228
2229                            // Open the calendar
2230                            $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2231
2232                            if($calFolder) {
2233                                if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops))
2234                                    $conflicting = true;
2235                            } else {
2236                                $this->errorSetResource = 1; // No access
2237                            }
2238
2239                            if($conflicting){
2240                                $this->errorSetResource = 4; // Conflict
2241                            }
2242                        }
2243                    }
2244                }
2245            }
2246
2247            if(!$this->errorSetResource && $accessToFolder){
2248                /**
2249                 * First search on GlobalID(0x3)
2250                 * If (recurring and occurrence) If Resource was booked for only this occurrence then Resource should have only this occurrence in Calendar and not whole series.
2251                 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesnt matter if search is based on GlobalID.
2252                 */
2253                $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2254
2255                /**
2256                 * If no entry is found then
2257                 * 1) Resource doesnt have meeting in Calendar. Seriously!!
2258                 * OR
2259                 * 2) We were looking for occurrence item but Resource has whole series
2260                 */
2261                if(empty($rows)){
2262                    /**
2263                     * Now search on CleanGlobalID(0x23) WHY???
2264                     * Because we are looking recurring item
2265                     *
2266                     * Possible results of this search
2267                     * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2268                     * 2) If Resource was booked for whole series then it should return series.
2269                     */
2270                    $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2271
2272                    $newResourceMsg = false;
2273                    if (!empty($rows)) {
2274                        // Since we are looking for recurring item, open every result and check for 'recurring' property.
2275                        foreach($rows as $row) {
2276                            $ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2277                            $ResourceMsgProps = mapi_getprops($ResourceMsg, array($this->proptags['recurring']));
2278
2279                            if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2280                                $newResourceMsg = $ResourceMsg;
2281                                break;
2282                            }
2283                        }
2284                    }
2285
2286                    // Still no results found. I giveup, create new message.
2287                    if (!$newResourceMsg)
2288                        $newResourceMsg = mapi_folder_createmessage($calFolder);
2289                }else{
2290                    $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2291                }
2292
2293                // Prefix the subject if needed
2294                if($prefix && isset($messageprops[PR_SUBJECT])) {
2295                    $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2296                }
2297
2298                // Set status to cancelled if needed
2299                $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2300                if($cancel) {
2301                    $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2302                    $messageprops[$this->proptags['busystatus']] = fbFree; // Free
2303                } else {
2304                    $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2305                }
2306                $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource autmatically accepts the appointment
2307
2308                $messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
2309
2310                // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
2311                // confuses the Zarafa webaccess
2312                $messageprops[PR_ICON_INDEX] = null;
2313                $messageprops[PR_RESPONSE_REQUESTED] = true;
2314
2315                $addrinfo = $this->getOwnerAddress($this->store);
2316
2317                if($addrinfo) {
2318                    list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2319
2320                    $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2321                    $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2322                    $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2323                    $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2324                    $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2325
2326                    $messageprops[$this->proptags['apptreplyname']] = $ownername;
2327                    $messageprops[$this->proptags['replytime']] = time();
2328                }
2329
2330                if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2331                    $recurr = new Recurrence($userStore, $newResourceMsg);
2332
2333                    // Copy recipients list
2334                    $reciptable = mapi_message_getrecipienttable($message);
2335                    $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2336                    // add owner to recipient table
2337                    $this->addOrganizer($messageprops, $recips, true);
2338
2339                    // Update occurrence
2340                    if($recurr->isException($basedate))
2341                        $recurr->modifyException($messageprops, $basedate, $recips);
2342                    else
2343                        $recurr->createException($messageprops, $basedate, false, $recips);
2344                } else {
2345
2346                    mapi_setprops($newResourceMsg, $messageprops);
2347
2348                    // Copy attachments
2349                    $this->replaceAttachments($message, $newResourceMsg);
2350
2351                    // Copy all recipients too
2352                    $this->replaceRecipients($message, $newResourceMsg);
2353
2354                    // Now add organizer also to recipient table
2355                    $recips = Array();
2356                    $this->addOrganizer($messageprops, $recips);
2357                    mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2358                }
2359
2360                mapi_savechanges($newResourceMsg);
2361
2362                $resourceRecipData[] = Array(
2363                    'store' => $userStore,
2364                    'folder' => $calFolder,
2365                    'msg' => $newResourceMsg,
2366                );
2367                $this->includesResources = true;
2368            }else{
2369                /**
2370                 * If no other errors occured and you have no access to the
2371                 * folder of the resource, throw an error=1.
2372                 */
2373                if(!$this->errorSetResource){
2374                    $this->errorSetResource = 1;
2375                }
2376
2377                for($j = 0, $len = count($resourceRecipData); $j < $len; $j++){
2378                    // Get the EntryID
2379                    $props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2380
2381                    mapi_folder_deletemessages($resourceRecipData[$j]['folder'], Array($props[PR_ENTRYID]), DELETE_HARD_DELETE);
2382                }
2383                $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2384            }
2385            $i++;
2386        }
2387
2388        /**************************************************************
2389         * Set the BCC-recipients (resources) tackstatus to accepted.
2390         */
2391        // Get resource recipients
2392        $getResourcesRestriction = Array(RES_AND,
2393            Array(Array(RES_PROPERTY,
2394                Array(RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2395                    ULPROPTAG => PR_RECIPIENT_TYPE,
2396                    VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
2397                )
2398            ))
2399        );
2400        $recipienttable = mapi_message_getrecipienttable($message);
2401        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2402        if(!empty($resourceRecipients)){
2403            // Set Tracking status of resource recipients to olResponseAccepted (3)
2404            for($i = 0, $len = count($resourceRecipients); $i < $len; $i++){
2405                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2406                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2407            }
2408            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2409        }
2410
2411        // Publish updated free/busy information
2412        if(!$this->errorSetResource){
2413            for($i = 0, $len = count($resourceRecipData); $i < $len; $i++){
2414                $storeProps = mapi_msgstore_getprops($resourceRecipData[$i]['store'], array(PR_MAILBOX_OWNER_ENTRYID));
2415                if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])){
2416                    $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2417                    $pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
2418                }
2419            }
2420        }
2421
2422        return $resourceRecipData;
2423    }
2424    /**
2425     * Function which save an exception into recurring item
2426     *
2427     * @param resource $recurringItem reference to MAPI_message of recurring item
2428     * @param resource $occurrenceItem reference to MAPI_message of occurrence
2429     * @param string $basedate basedate of occurrence
2430     * @param boolean $move if true then occurrence item is deleted
2431     * @param boolean $tentative true if user has tentatively accepted it or false if user has accepted it.
2432     * @param boolean $userAction true if user has manually responded to meeting request
2433     * @param resource $store user store
2434     * @param boolean $isDelegate true if delegate is processing this meeting request
2435     */
2436    function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false)
2437    {
2438        $recurr = new Recurrence($store, $recurringItem);
2439
2440        // Copy properties from meeting request
2441        $exception_props = mapi_getprops($occurrenceItem);
2442
2443        // Copy recipients list
2444        $reciptable = mapi_message_getrecipienttable($occurrenceItem);
2445        // If delegate, then do not add the delegate in recipients
2446        if ($isDelegate) {
2447            $delegate = mapi_getprops($this->message, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
2448            $res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
2449            $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2450        } else {
2451            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2452        }
2453
2454
2455        // add owner to recipient table
2456        $this->addOrganizer($exception_props, $recips, true);
2457
2458        // add delegator to meetings
2459        if ($isDelegate) $this->addDelegator($exception_props, $recips);
2460
2461        $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2462        $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2463        // Set basedate property (ExceptionReplaceTime)
2464
2465        if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2466            if($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2467                $exception_props[$this->proptags['busystatus']] = $tentative;
2468            } else {
2469                $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2470            }
2471            // we already have intendedbusystatus value in $exception_props so no need to copy it
2472        } else {
2473            $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2474        }
2475
2476        if($userAction) {
2477            // if user has responded then set replytime
2478            $exception_props[$this->proptags['replytime']] = time();
2479        }
2480
2481        if($recurr->isException($basedate))
2482            $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2483        else
2484            $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2485
2486        // Move the occurrenceItem to the waste basket
2487        if ($move) {
2488            $wastebasket = $this->openDefaultWastebasket();
2489            $sourcefolder = mapi_msgstore_openentry($this->store, $exception_props[PR_PARENT_ENTRYID]);
2490            mapi_folder_copymessages($sourcefolder, Array($exception_props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
2491        }
2492
2493        mapi_savechanges($recurringItem);
2494    }
2495
2496    /**
2497     * Function which submits meeting request based on arguments passed to it.
2498     *@param resource $message MAPI_message whose meeting request is to be send
2499     *@param boolean $cancel if true send request, else send cancellation
2500     *@param string $prefix subject prefix
2501     *@param integer $basedate basedate for an occurrence
2502     *@param Object $recurObject recurrence object of mr
2503     *@param boolean $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2504     */
2505    function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false)
2506    {
2507        $newmessageprops = $messageprops = mapi_getprops($this->message);
2508        $new = $this->createOutgoingMessage();
2509
2510        // Copy the entire message into the new meeting request message
2511        if ($basedate) {
2512            // messageprops contains properties of whole recurring series
2513            // and newmessageprops contains properties of exception item
2514            $newmessageprops = mapi_getprops($message);
2515
2516            // Ensure that the correct basedate is set in the new message
2517            $newmessageprops[$this->proptags['basedate']] = $basedate;
2518
2519            // Set isRecurring to false, because this is an exception
2520            $newmessageprops[$this->proptags['recurring']] = false;
2521
2522            // set LID_IS_EXCEPTION to true
2523            $newmessageprops[$this->proptags['is_exception']] = true;
2524
2525            // Set to high importance
2526            if($cancel) $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2527
2528            // Set startdate and enddate of exception
2529            if ($cancel && $recurObject) {
2530                $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2531                $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2532            }
2533
2534            // Set basedate in guid (0x3)
2535            $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2536            $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2537            $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2538
2539            // Get deleted recipiets from exception msg
2540            $restriction = Array(RES_AND,
2541                            Array(
2542                                Array(RES_BITMASK,
2543                                    Array(    ULTYPE        =>    BMR_NEZ,
2544                                            ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
2545                                            ULMASK        =>    recipExceptionalDeleted
2546                                    )
2547                                ),
2548                                Array(RES_BITMASK,
2549                                    Array(    ULTYPE        =>    BMR_EQZ,
2550                                            ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
2551                                            ULMASK        =>    recipOrganizer
2552                                    )
2553                                ),
2554                            )
2555            );
2556
2557            // In direct-booking mode, we don't need to send cancellations to resources
2558            if($this->enableDirectBooking) {
2559                $restriction[1][] = Array(RES_PROPERTY,
2560                                        Array(RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
2561                                            ULPROPTAG => PR_RECIPIENT_TYPE,
2562                                            VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
2563                                        )
2564                                    );
2565            }
2566
2567            $recipienttable = mapi_message_getrecipienttable($message);
2568            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2569
2570            if (!$deletedRecips) {
2571                $deletedRecips = array_merge(array(), $recipients);
2572            } else {
2573                $deletedRecips = array_merge($deletedRecips, $recipients);
2574            }
2575        }
2576
2577        // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
2578        // confuses the Zarafa webaccess
2579        $newmessageprops[PR_ICON_INDEX] = null;
2580        $newmessageprops[PR_RESPONSE_REQUESTED] = true;
2581
2582        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2583        $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
2584        $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
2585
2586        // Set updatecounter/AppointmentSequenceNumber
2587        // get the value of latest updatecounter for the whole series and use it
2588        $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
2589
2590        $meetingTimeInfo = $this->getMeetingTimeInfo();
2591
2592        if($meetingTimeInfo)
2593            $newmessageprops[PR_BODY] = $meetingTimeInfo;
2594
2595        // Send all recurrence info in mail, if this is a recurrence meeting.
2596        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
2597            if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
2598                $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2599            }
2600            $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2601            $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2602            $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2603
2604            if($recurObject) {
2605                $this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
2606            }
2607        }
2608
2609        if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
2610            unset($newmessageprops[$this->proptags['counter_proposal']]);
2611        }
2612
2613        // Prefix the subject if needed
2614        if ($prefix && isset($newmessageprops[PR_SUBJECT]))
2615            $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
2616
2617        mapi_setprops($new, $newmessageprops);
2618
2619        // Copy attachments
2620        $this->replaceAttachments($message, $new, $copyExceptions);
2621
2622        // Retrieve only those recipient who should receive this meeting request.
2623        $stripResourcesRestriction = Array(RES_AND,
2624                                Array(
2625                                    Array(RES_BITMASK,
2626                                        Array(    ULTYPE        =>    BMR_EQZ,
2627                                                ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
2628                                                ULMASK        =>    recipExceptionalDeleted
2629                                        )
2630                                    ),
2631                                    Array(RES_BITMASK,
2632                                        Array(    ULTYPE        =>    BMR_EQZ,
2633                                                ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
2634                                                ULMASK        =>    recipOrganizer
2635                                        )
2636                                    ),
2637                                )
2638        );
2639
2640        // In direct-booking mode, resources do not receive a meeting request
2641        if($this->enableDirectBooking) {
2642            $stripResourcesRestriction[1][] =
2643                                    Array(RES_PROPERTY,
2644                                        Array(RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
2645                                            ULPROPTAG => PR_RECIPIENT_TYPE,
2646                                            VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
2647                                        )
2648                                    );
2649        }
2650
2651        $recipienttable = mapi_message_getrecipienttable($message);
2652        $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2653
2654        if ($basedate && empty($recipients)) {
2655            // Retrieve full list
2656            $recipienttable = mapi_message_getrecipienttable($this->message);
2657            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2658
2659            // Save recipients in exceptions
2660            mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
2661
2662            // Now retrieve only those recipient who should receive this meeting request.
2663            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2664        }
2665
2666        //@TODO: handle nonAcceptingResources
2667        /**
2668         * Add resource recipients that did not automatically accept the meeting request.
2669         * (note: meaning that they did not decline the meeting request)
2670         *//*
2671        for($i=0;$i<count($this->nonAcceptingResources);$i++){
2672            $recipients[] = $this->nonAcceptingResources[$i];
2673        }*/
2674
2675        if(!empty($recipients)) {
2676            // Strip out the sender/"owner" recipient
2677            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
2678
2679            // Set some properties that are different in the sent request than
2680            // in the item in our calendar
2681
2682            // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
2683            // should always be fbTentative
2684            $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
2685            $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
2686            $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
2687            $newmessageprops[$this->proptags['attendee_critical_change']] = time();
2688            $newmessageprops[$this->proptags['owner_critical_change']] = time();
2689            $newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
2690
2691            if ($cancel) {
2692                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2693                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2694                $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2695            } else {
2696                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
2697                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2698            }
2699
2700            mapi_setprops($new, $newmessageprops);
2701            mapi_message_savechanges($new);
2702
2703            // Submit message to non-resource recipients
2704            mapi_message_submitmessage($new);
2705        }
2706
2707        // Send cancellation to deleted attendees
2708        if ($deletedRecips && !empty($deletedRecips)) {
2709            $new = $this->createOutgoingMessage();
2710
2711            mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
2712
2713            $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2714            $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2715            $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2716            $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;    // HIGH Importance
2717            if (isset($newmessageprops[PR_SUBJECT])) {
2718                $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
2719            }
2720
2721            mapi_setprops($new, $newmessageprops);
2722            mapi_message_savechanges($new);
2723
2724            // Submit message to non-resource recipients
2725            mapi_message_submitmessage($new);
2726        }
2727
2728        // Set properties on meeting object in calendar
2729        // Set requestsent to 'true' (turns on 'tracking', etc)
2730        $props = array();
2731        $props[$this->proptags['meetingstatus']] = olMeeting;
2732        $props[$this->proptags['responsestatus']] = olResponseOrganized;
2733        $props[$this->proptags['requestsent']] = (!empty($recipients)) || ($this->includesResources && !$this->errorSetResource);
2734        $props[$this->proptags['attendee_critical_change']] = time();
2735        $props[$this->proptags['owner_critical_change']] = time();
2736        $props[$this->proptags['meetingtype']] = mtgRequest;
2737        // save the new updatecounter to exception/recurring series/normal meeting
2738        $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
2739
2740        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2741        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2742        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2743
2744        mapi_setprops($message, $props);
2745
2746        // saving of these properties on calendar item should be handled by caller function
2747        // based on sending meeting request was successfull or not
2748    }
2749
2750    /**
2751     * OL2007 uses these 4 properties to specify occurence that should be updated.
2752     * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
2753     * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
2754     * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
2755     * also additionally we are sending these properties.
2756     * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID
2757     * @param Object $recurObject instance of recurrence class for this message
2758     * @param Array $messageprops properties of meeting object that is going to be send
2759     * @param Array $newmessageprops properties of meeting request/response that is going to be send
2760     */
2761    function generateRecurDates($recurObject, $messageprops, &$newmessageprops)
2762    {
2763        if($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
2764            $startDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
2765            $endDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
2766
2767            $startDate = explode(":", $startDate);
2768            $endDate = explode(":", $endDate);
2769
2770            // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
2771            // RecurStartDate = year * 512 + month_number * 32 + day_number
2772            $newmessageprops[$this->proptags["start_recur_date"]] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
2773            // RecurStartTime = hour * 4096 + minutes * 64 + seconds
2774            $newmessageprops[$this->proptags["start_recur_time"]] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
2775
2776            $newmessageprops[$this->proptags["end_recur_date"]] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
2777            $newmessageprops[$this->proptags["end_recur_time"]] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
2778        }
2779    }
2780
2781    function createOutgoingMessage()
2782    {
2783        $sentprops = array();
2784        $outbox = $this->openDefaultOutbox($this->openDefaultStore());
2785
2786        $outgoing = mapi_folder_createmessage($outbox);
2787        if(!$outgoing) return false;
2788
2789        $addrinfo = $this->getOwnerAddress($this->store);
2790        if($addrinfo) {
2791            list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2792            $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2793            $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2794            $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2795            $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2796            $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2797        }
2798
2799        $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
2800
2801        mapi_setprops($outgoing, $sentprops);
2802
2803        return $outgoing;
2804    }
2805
2806    /**
2807     * Function which checks received meeting request is either old(outofdate) or new.
2808     * @return boolean true if meeting request is outofdate else false if it is new
2809     */
2810    function isMeetingOutOfDate()
2811    {
2812        $result = false;
2813        $store = $this->store;
2814        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']));
2815
2816        if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
2817            return true;
2818        }
2819
2820        // get the basedate to check for exception
2821        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
2822
2823        $calendarItems = $this->getCorrespondedCalendarItems();
2824
2825        foreach($calendarItems as $calendarItem) {
2826            if ($calendarItem) {
2827                $calendarItemProps = mapi_getprops($calendarItem, array(
2828                                                        $this->proptags['owner_critical_change'],
2829                                                        $this->proptags['updatecounter'],
2830                                                        $this->proptags['recurring']
2831                                        ));
2832
2833                // If these items is recurring and basedate is found then open exception to compare it with meeting request
2834                if (isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] && $basedate) {
2835                    $recurr = new Recurrence($store, $calendarItem);
2836
2837                    if ($recurr->isException($basedate)) {
2838                        $attach = $recurr->getExceptionAttachment($basedate);
2839                        $exception = mapi_attach_openobj($attach, 0);
2840                        $occurrenceItemProps = mapi_getprops($exception, array(
2841                                                        $this->proptags['owner_critical_change'],
2842                                                        $this->proptags['updatecounter']
2843                                            ));
2844                    }
2845
2846                    // we found the exception, compare with it
2847                    if(isset($occurrenceItemProps)) {
2848                        if ((isset($occurrenceItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $occurrenceItemProps[$this->proptags['updatecounter']])
2849                            || (isset($occurrenceItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $occurrenceItemProps[$this->proptags['owner_critical_change']])) {
2850
2851                            mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2852                            mapi_savechanges($this->message);
2853                            $result = true;
2854                        }
2855                    } else {
2856                        // we are not able to find exception, could mean that a significant change has occured on series
2857                        // and it deleted all exceptions, so compare with series
2858                        if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
2859                            || (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2860
2861                            mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2862                            mapi_savechanges($this->message);
2863                            $result = true;
2864                        }
2865                    }
2866                } else {
2867                    // normal / recurring series
2868                    if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
2869                            || (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2870
2871                        mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2872                        mapi_savechanges($this->message);
2873                        $result = true;
2874                    }
2875                }
2876            }
2877        }
2878
2879        return $result;
2880    }
2881
2882    /**
2883     * Function which checks received meeting request is updated later or not.
2884     * @return boolean true if meeting request is updated later.
2885     * @TODO: Implement handling for recurrings and exceptions.
2886     */
2887    function isMeetingUpdated()
2888    {
2889        $result = false;
2890        $store = $this->store;
2891        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['owner_critical_change'], $this->proptags['updatecounter']));
2892
2893        $calendarItems = $this->getCorrespondedCalendarItems();
2894
2895        foreach($calendarItems as $calendarItem) {
2896            if ($calendarItem) {
2897                $calendarItemProps = mapi_getprops($calendarItem, array(
2898                                                    $this->proptags['updatecounter'],
2899                                                    $this->proptags['recurring']
2900                                    ));
2901
2902                if(isset($calendarItemProps[$this->proptags['updatecounter']]) && isset($props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
2903                    $result = true;
2904                }
2905            }
2906        }
2907
2908        return $result;
2909    }
2910
2911    /**
2912     * Checks if there has been any significant changes on appointment/meeting item.
2913     * Significant changes be:
2914     * 1) startdate has been changed
2915     * 2) duedate has been changed OR
2916     * 3) recurrence pattern has been created, modified or removed
2917     *
2918     * @param Array oldProps old props before an update
2919     * @param Number basedate basedate
2920     * @param Boolean isRecurrenceChanged for change in recurrence pattern.
2921     * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
2922     */
2923    function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false)
2924    {
2925        $message = null;
2926        $attach = null;
2927
2928        // If basedate is specified then we need to open exception message to clear recipient responses
2929        if($basedate) {
2930            $recurrence = new Recurrence($this->store, $this->message);
2931            if($recurrence->isException($basedate)){
2932                $attach = $recurrence->getExceptionAttachment($basedate);
2933                if ($attach) {
2934                    $message = mapi_attach_openobj($attach, MAPI_MODIFY);
2935                }
2936            }
2937        } else {
2938            // use normal message or recurring series message
2939            $message = $this->message;
2940        }
2941
2942        if(!$message) {
2943            return;
2944        }
2945
2946        $newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']));
2947
2948        // Check whether message is updated or not.
2949        if(isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
2950            return;
2951        }
2952
2953        if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']])
2954            || ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']])
2955            || $isRecurrenceChanged) {
2956            $this->clearRecipientResponse($message);
2957
2958            mapi_setprops($message, array($this->proptags['owner_critical_change'] => time()));
2959
2960            mapi_savechanges($message);
2961            if ($attach) { // Also save attachment Object.
2962                mapi_savechanges($attach);
2963            }
2964        }
2965    }
2966
2967    /**
2968     * Clear responses of all attendees who have replied in past.
2969     * @param MAPI_MESSAGE $message on which responses should be cleared
2970     */
2971    function clearRecipientResponse($message)
2972    {
2973        $recipTable = mapi_message_getrecipienttable($message);
2974        $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
2975
2976        foreach($recipsRows as $recipient) {
2977            if(($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer){
2978                // Recipient is attendee, set the trackstatus to "Not Responded"
2979                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2980            } else {
2981                // Recipient is organizer, this is not possible, but for safety
2982                // it is best to clear the trackstatus for him as well by setting
2983                // the trackstatus to "Organized".
2984                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2985            }
2986            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, array($recipient));
2987        }
2988    }
2989
2990    /**
2991     * Function returns corresponded calendar items attached with
2992     * the meeting request.
2993     * @return Array array of correlated calendar items.
2994     */
2995    function getCorrespondedCalendarItems()
2996    {
2997        $store = $this->store;
2998        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
2999
3000        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3001
3002        // If Delegate is processing mr for Delegator then retrieve Delegator's store and calendar.
3003        if (isset($props[PR_RCVD_REPRESENTING_NAME])) {
3004            $delegatorStore = $this->getDelegatorStore($props);
3005            $store = $delegatorStore['store'];
3006            $calFolder = $delegatorStore['calFolder'];
3007        } else {
3008            $calFolder = $this->openDefaultCalendar();
3009        }
3010
3011        // Finding item in calendar with GlobalID(0x3), not necessary that attendee is having recurring item, he/she can also have only a occurrence
3012        $entryids = $this->findCalendarItems($props[$this->proptags['goid']], $calFolder);
3013
3014        // Basedate found, so this meeting request is an update of an occurrence.
3015        if ($basedate) {
3016            if (!$entryids) {
3017                // Find main recurring item in calendar with GlobalID(0x23)
3018                $entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
3019            }
3020        }
3021
3022        $calendarItems = array();
3023        if ($entryids) {
3024            foreach($entryids as $entryid) {
3025                $calendarItems[] = mapi_msgstore_openentry($store, $entryid);
3026            }
3027        }
3028
3029        return $calendarItems;
3030    }
3031
3032    /**
3033     * Function which checks whether received meeting request is either conflicting with other appointments or not.
3034     *@return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3035     * conflict of recurring meeting and false if meeting is not conflicting.
3036     */
3037    function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false)
3038    {
3039        $returnValue = false;
3040        $conflicting = false;
3041        $noOfInstances = 0;
3042
3043        if (!$message) $message = $this->message;
3044
3045        if (!$userStore) $userStore = $this->store;
3046
3047        if (!$calFolder) {
3048            $root = mapi_msgstore_openentry($userStore);
3049            $rootprops = mapi_getprops($root, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
3050
3051            if(!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
3052                return;
3053            }
3054
3055            $calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
3056        }
3057
3058        if (!$msgprops) $msgprops = mapi_getprops($message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['recurring'], $this->proptags['clipstart'], $this->proptags['clipend']));
3059
3060        if ($calFolder) {
3061            // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3062            if (isset($msgprops[$this->proptags['recurring']]) && $msgprops[$this->proptags['recurring']]) {
3063                // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3064                $recurr = new Recurrence($userStore, $message);
3065                $items = $recurr->getItems($msgprops[$this->proptags['clipstart']], $msgprops[$this->proptags['clipend']] * (24*24*60), 30);
3066
3067                foreach ($items as $item) {
3068                    // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3069                    $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
3070
3071                    foreach ($calendarItems as $calendarItem) {
3072                        if ($calendarItem[$this->proptags['busystatus']] != fbFree) {
3073                            /**
3074                             * Only meeting requests have globalID, normal appointments do not have globalID
3075                             * so if any normal appointment if found then it is assumed to be conflict.
3076                             */
3077                            if(isset($calendarItem[$this->proptags['goid']])) {
3078                                if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
3079                                    $noOfInstances++;
3080                                    break;
3081                                }
3082                            } else {
3083                                $noOfInstances++;
3084                                break;
3085                            }
3086                        }
3087                    }
3088                }
3089
3090                $returnValue = $noOfInstances;
3091            } else {
3092                // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3093                $items = getCalendarItems($userStore, $calFolder, $msgprops[$this->proptags['startdate']], $msgprops[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
3094
3095                foreach($items as $item) {
3096                    if ($item[$this->proptags['busystatus']] != fbFree) {
3097                        if(isset($item[$this->proptags['goid']])) {
3098                            if (($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']])
3099                                && ($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid2']])) {
3100                                $conflicting = true;
3101                                break;
3102                            }
3103                        } else {
3104                            $conflicting = true;
3105                            break;
3106                        }
3107                    }
3108                }
3109
3110                if ($conflicting) $returnValue = true;
3111            }
3112        }
3113        return $returnValue;
3114    }
3115
3116    /**
3117     *  Function which adds organizer to recipient list which is passed.
3118     *  This function also checks if it has organizer.
3119     *
3120     * @param array $messageProps message properties
3121     * @param array $recipients    recipients list of message.
3122     * @param boolean $isException true if we are processing recipient of exception
3123     */
3124    function addDelegator($messageProps, &$recipients)
3125    {
3126        $hasDelegator = false;
3127        // Check if meeting already has an organizer.
3128        foreach ($recipients as $key => $recipient){
3129            if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS])
3130                $hasDelegator = true;
3131        }
3132
3133        if (!$hasDelegator){
3134            // Create delegator.
3135            $delegator = array();
3136            $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3137            $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3138            $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3139            $delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3140            $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3141            $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3142            $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3143            $delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3144
3145            // Add organizer to recipients list.
3146            array_unshift($recipients, $delegator);
3147        }
3148    }
3149
3150    function getDelegatorStore($messageprops)
3151    {
3152        // Find the organiser of appointment in addressbook
3153        $delegatorName = array(array(PR_DISPLAY_NAME => $messageprops[PR_RCVD_REPRESENTING_NAME]));
3154        $ab = mapi_openaddressbook($this->session);
3155        $user = mapi_ab_resolvename($ab, $delegatorName, EMS_AB_ADDRESS_LOOKUP);
3156
3157        // Get StoreEntryID by username
3158        $delegatorEntryid = mapi_msgstore_createentryid($this->store, $user[0][PR_EMAIL_ADDRESS]);
3159        // Open store of the delegator
3160        $delegatorStore = mapi_openmsgstore($this->session, $delegatorEntryid);
3161        // Open root folder
3162        $delegatorRoot = mapi_msgstore_openentry($delegatorStore, null);
3163        // Get calendar entryID
3164        $delegatorRootProps = mapi_getprops($delegatorRoot, array(PR_IPM_APPOINTMENT_ENTRYID));
3165        // Open the calendar Folder
3166        $calFolder = mapi_msgstore_openentry($delegatorStore, $delegatorRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
3167
3168        return Array('store' => $delegatorStore, 'calFolder' => $calFolder);
3169    }
3170
3171    /**
3172     * Function returns extra info about meeting timing along with message body
3173     * which will be included in body while sending meeting request/response.
3174     *
3175     * @return string $meetingTimeInfo info about meeting timing along with message body
3176     */
3177    function getMeetingTimeInfo()
3178    {
3179        return $this->meetingTimeInfo;
3180    }
3181
3182    /**
3183     * Function sets extra info about meeting timing along with message body
3184     * which will be included in body while sending meeting request/response.
3185     *
3186     * @param string $meetingTimeInfo info about meeting timing along with message body
3187     */
3188    function setMeetingTimeInfo($meetingTimeInfo)
3189    {
3190        $this->meetingTimeInfo = $meetingTimeInfo;
3191    }
3192}
3193?>
Note: See TracBrowser for help on using the repository browser.