~olivier-chatelain/php-addressbook/trunk

« back to all changes in this revision

Viewing changes to z-push/backend/zarafa/mapi/class.meetingrequest.php

  • Committer: chatelao
  • Date: 2016-09-11 07:59:37 UTC
  • Revision ID: git-v1:03926e44d99a04a3b05d4761e322a1f5e8a405d7
Chg: Upgrade z-push from 2.0.3 to 2.2.12 (stable)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/*
 
3
 * Copyright 2005 - 2013  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
 
 
51
class 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
/*
 
492
If 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
                        // ZP-341 - we need to copy as well the attachments
 
944
                        // Copy attachments too
 
945
                        $this->replaceAttachments($this->message, $new);
 
946
                        // ZP-341 - end
 
947
 
 
948
                        if($userAction) {
 
949
                            // if user has responded then set replytime
 
950
                            $props[$this->proptags['replytime']] = time();
 
951
                        }
 
952
 
 
953
                        mapi_setprops($new, $proposeNewTimeProps + $props);
 
954
 
 
955
                        $reciptable = mapi_message_getrecipienttable($this->message);
 
956
 
 
957
                        $recips = array();
 
958
                        if(!$isDelegate)
 
959
                            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
 
960
 
 
961
                        $this->addOrganizer($props, $recips);
 
962
 
 
963
                        if($isDelegate) {
 
964
                            /**
 
965
                             * If user is delegate then remove that user from recipienttable of the MR.
 
966
                             * and delegate MR mail doesn't contain any of the attendees in recipient table.
 
967
                             * So, other required and optional attendees are added from
 
968
                             * toattendeesstring and ccattendeesstring properties.
 
969
                             */
 
970
                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
 
971
                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
 
972
                            mapi_message_modifyrecipients($new, 0, $recips);
 
973
                        } else {
 
974
                            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
 
975
                        }
 
976
                        mapi_message_savechanges($new);
 
977
 
 
978
                        $props = mapi_getprops($new, array(PR_ENTRYID));
 
979
                        $entryid = $props[PR_ENTRYID];
 
980
                    }
 
981
                }
 
982
            }
 
983
        } else {
 
984
            // Here only properties are set on calendaritem, because user is responding from calendar.
 
985
            $props = array();
 
986
            $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
 
987
 
 
988
            if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
 
989
                if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
 
990
                    $props[$this->proptags['busystatus']] = $tentative;
 
991
                } else {
 
992
                    $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
 
993
                }
 
994
                $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
 
995
            } else {
 
996
                $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
 
997
            }
 
998
 
 
999
            $props[$this->proptags['meetingstatus']] = olMeetingReceived;
 
1000
            $props[$this->proptags['replytime']] = time();
 
1001
 
 
1002
            if ($basedate) {
 
1003
                $recurr = new Recurrence($store, $this->message);
 
1004
 
 
1005
                // Copy recipients list
 
1006
                $reciptable = mapi_message_getrecipienttable($this->message);
 
1007
                $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
 
1008
 
 
1009
                if($recurr->isException($basedate)) {
 
1010
                    $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
 
1011
                } else {
 
1012
                    $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
 
1013
                    $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
 
1014
 
 
1015
                    $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
 
1016
                    $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
 
1017
                    $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
 
1018
                    $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
 
1019
 
 
1020
                    $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
 
1021
                }
 
1022
            } else {
 
1023
                mapi_setprops($this->message, $proposeNewTimeProps + $props);
 
1024
            }
 
1025
            mapi_savechanges($this->message);
 
1026
 
 
1027
            $entryid = $messageprops[PR_ENTRYID];
 
1028
        }
 
1029
 
 
1030
        return $entryid;
 
1031
    }
 
1032
 
 
1033
    /**
 
1034
     * Declines the meeting request by moving the item to the deleted
 
1035
     * items folder and sending a decline message. After declining, you
 
1036
     * can't use this class instance any more. The message is closed.
 
1037
     * When an occurrence is decline then false is returned because that
 
1038
     * occurrence is deleted not the recurring item.
 
1039
     *
 
1040
     *@param boolean $sendresponse true if a response has to be sent to organizer
 
1041
     *@param resource $store MAPI_store of user
 
1042
     *@param string $basedate if specified contains starttime of day of an occurrence
 
1043
     *@return boolean true if item is deleted from Calendar else false
 
1044
     */
 
1045
    function doDecline($sendresponse, $store=false, $basedate = false, $body = false)
 
1046
    {
 
1047
        $result = true;
 
1048
        $calendaritem = false;
 
1049
        if($this->isLocalOrganiser())
 
1050
            return;
 
1051
 
 
1052
        // Remove any previous calendar items with this goid and appt id
 
1053
        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
 
1054
 
 
1055
        // If this meeting request is received by a delegate then open delegator's store.
 
1056
        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
 
1057
            $delegatorStore = $this->getDelegatorStore($messageprops);
 
1058
 
 
1059
            $store = $delegatorStore['store'];
 
1060
            $calFolder = $delegatorStore['calFolder'];
 
1061
        } else {
 
1062
            $calFolder = $this->openDefaultCalendar();
 
1063
            $store = $this->store;
 
1064
        }
 
1065
 
 
1066
        $goid = $messageprops[$this->proptags['goid']];
 
1067
 
 
1068
        // First, find the items in the calendar by GlobalObjid (0x3)
 
1069
        $entryids = $this->findCalendarItems($goid, $calFolder);
 
1070
 
 
1071
        if (!$basedate)
 
1072
            $basedate = $this->getBasedateFromGlobalID($goid);
 
1073
 
 
1074
        if($sendresponse)
 
1075
            $this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
 
1076
 
 
1077
        if ($basedate) {
 
1078
            // use CleanGlobalObjid (0x23)
 
1079
            $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
 
1080
 
 
1081
            foreach($calendaritems as $entryid) {
 
1082
                // Open each calendar item and set the properties of the cancellation object
 
1083
                $calendaritem = mapi_msgstore_openentry($store, $entryid);
 
1084
 
 
1085
                // Recurring item is found, now delete exception
 
1086
                if ($calendaritem)
 
1087
                    $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
 
1088
            }
 
1089
 
 
1090
            if ($this->isMeetingRequest())
 
1091
                $calendaritem = false;
 
1092
            else
 
1093
                $result = false;
 
1094
        }
 
1095
 
 
1096
        if (!$calendaritem) {
 
1097
            $calendar = $this->openDefaultCalendar();
 
1098
 
 
1099
            if(!empty($entryids)) {
 
1100
                mapi_folder_deletemessages($calendar, $entryids);
 
1101
            }
 
1102
 
 
1103
            // All we have to do to decline, is to move the item to the waste basket
 
1104
            $wastebasket = $this->openDefaultWastebasket();
 
1105
            $sourcefolder = $this->openParentFolder();
 
1106
 
 
1107
            $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
 
1108
 
 
1109
            // Release the message
 
1110
            $this->message = null;
 
1111
 
 
1112
            // Move the message to the waste basket
 
1113
            mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
 
1114
        }
 
1115
        return $result;
 
1116
    }
 
1117
 
 
1118
    /**
 
1119
     * Removes a meeting request from the calendar when the user presses the
 
1120
     * 'remove from calendar' button in response to a meeting cancellation.
 
1121
     * @param string $basedate if specified contains starttime of day of an occurrence
 
1122
     */
 
1123
    function doRemoveFromCalendar($basedate)
 
1124
    {
 
1125
        if($this->isLocalOrganiser())
 
1126
            return false;
 
1127
 
 
1128
        $store = $this->store;
 
1129
        $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_NAME, PR_MESSAGE_CLASS));
 
1130
        $goid = $messageprops[$this->proptags['goid']];
 
1131
 
 
1132
        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
 
1133
            $delegatorStore = $this->getDelegatorStore($messageprops);
 
1134
            $store = $delegatorStore['store'];
 
1135
            $calFolder = $delegatorStore['calFolder'];
 
1136
        } else {
 
1137
            $calFolder = $this->openDefaultCalendar();
 
1138
        }
 
1139
 
 
1140
        $wastebasket = $this->openDefaultWastebasket();
 
1141
        $sourcefolder = $this->openParentFolder();
 
1142
 
 
1143
        // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
 
1144
        if (strpos($messageprops[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') === 0) {
 
1145
            /**
 
1146
             * 'Remove from calendar' option from previewpane then we have to check GlobalID of this meeting request.
 
1147
             * If basedate found then open meeting from calendar and delete that occurence.
 
1148
             */
 
1149
            $basedate = false;
 
1150
            if ($goid) {
 
1151
                // Retrieve GlobalID and find basedate in it.
 
1152
                $basedate = $this->getBasedateFromGlobalID($goid);
 
1153
 
 
1154
                // Basedate found, Now find item.
 
1155
                if ($basedate) {
 
1156
                    $guid = $this->setBasedateInGlobalID($goid);
 
1157
 
 
1158
                    // First, find the items in the calendar by GOID
 
1159
                    $calendaritems = $this->findCalendarItems($guid, $calFolder);
 
1160
                    if(is_array($calendaritems)) {
 
1161
                        foreach($calendaritems as $entryid) {
 
1162
                            // Open each calendar item and set the properties of the cancellation object
 
1163
                            $calendaritem = mapi_msgstore_openentry($store, $entryid);
 
1164
 
 
1165
                            if ($calendaritem){
 
1166
                                $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
 
1167
                            }
 
1168
                        }
 
1169
                    }
 
1170
                }
 
1171
            }
 
1172
 
 
1173
            // It is normal/recurring meeting item.
 
1174
            if (!$basedate) {
 
1175
                if (!isset($calFolder)) $calFolder = $this->openDefaultCalendar();
 
1176
 
 
1177
                $entryids = $this->findCalendarItems($goid, $calFolder);
 
1178
 
 
1179
                if(is_array($entryids)){
 
1180
                    // Move the calendaritem to the waste basket
 
1181
                    mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
 
1182
                }
 
1183
            }
 
1184
 
 
1185
            // Release the message
 
1186
            $this->message = null;
 
1187
 
 
1188
            // Move the message to the waste basket
 
1189
            mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
 
1190
 
 
1191
        } else {
 
1192
            // Here only properties are set on calendaritem, because user is responding from calendar.
 
1193
            if ($basedate) { //remove the occurence
 
1194
                $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
 
1195
            } else { //remove normal/recurring meeting item.
 
1196
                // Move the message to the waste basket
 
1197
                mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
 
1198
            }
 
1199
        }
 
1200
    }
 
1201
 
 
1202
    /**
 
1203
     * Removes the meeting request by moving the item to the deleted
 
1204
     * items folder. After canceling, youcan't use this class instance
 
1205
     * any more. The message is closed.
 
1206
     */
 
1207
    function doCancel()
 
1208
    {
 
1209
        if($this->isLocalOrganiser())
 
1210
            return;
 
1211
        if(!$this->isMeetingCancellation())
 
1212
            return;
 
1213
 
 
1214
        // Remove any previous calendar items with this goid and appt id
 
1215
        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
 
1216
        $goid = $messageprops[$this->proptags['goid']];
 
1217
 
 
1218
        $entryids = $this->findCalendarItems($goid);
 
1219
        $calendar = $this->openDefaultCalendar();
 
1220
 
 
1221
        mapi_folder_deletemessages($calendar, $entryids);
 
1222
 
 
1223
        // All we have to do to decline, is to move the item to the waste basket
 
1224
 
 
1225
        $wastebasket = $this->openDefaultWastebasket();
 
1226
        $sourcefolder = $this->openParentFolder();
 
1227
 
 
1228
        $messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
 
1229
 
 
1230
        // Release the message
 
1231
        $this->message = null;
 
1232
 
 
1233
        // Move the message to the waste basket
 
1234
        mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
 
1235
    }
 
1236
 
 
1237
 
 
1238
    /**
 
1239
     * Sets the properties in the message so that is can be sent
 
1240
     * as a meeting request. The caller has to submit the message. This
 
1241
     * is only used for new MeetingRequests. Pass the appointment item as $message
 
1242
     * in the constructor to do this.
 
1243
     */
 
1244
    function setMeetingRequest($basedate = false)
 
1245
    {
 
1246
        $props = mapi_getprops($this->message, Array($this->proptags['updatecounter']));
 
1247
 
 
1248
        // Create a new global id for this item
 
1249
        $goid = pack("H*", "040000008200E00074C5B7101A82E00800000000");
 
1250
        for ($i=0; $i<36; $i++)
 
1251
            $goid .= chr(rand(0, 255));
 
1252
 
 
1253
        // Create a new appointment id for this item
 
1254
        $apptid = rand();
 
1255
 
 
1256
        $props[PR_OWNER_APPT_ID] = $apptid;
 
1257
        $props[PR_ICON_INDEX] = 1026;
 
1258
        $props[$this->proptags['goid']] = $goid;
 
1259
        $props[$this->proptags['goid2']] = $goid;
 
1260
 
 
1261
        if (!isset($props[$this->proptags['updatecounter']])) {
 
1262
            $props[$this->proptags['updatecounter']] = 0;            // OL also starts sequence no with zero.
 
1263
            $props[$this->proptags['last_updatecounter']] = 0;
 
1264
        }
 
1265
 
 
1266
        mapi_setprops($this->message, $props);
 
1267
    }
 
1268
 
 
1269
    /**
 
1270
     * Sends a meeting request by copying it to the outbox, converting
 
1271
     * the message class, adding some properties that are required only
 
1272
     * for sending the message and submitting the message. Set cancel to
 
1273
     * true if you wish to completely cancel the meeting request. You can
 
1274
     * specify an optional 'prefix' to prefix the sent message, which is normally
 
1275
     * 'Canceled: '
 
1276
     */
 
1277
    function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false)
 
1278
    {
 
1279
        $this->includesResources = false;
 
1280
        $this->nonAcceptingResources = Array();
 
1281
 
 
1282
        // Get the properties of the message
 
1283
        $messageprops = mapi_getprops($this->message, Array($this->proptags['recurring']));
 
1284
 
 
1285
        /*****************************************************************************************
 
1286
         * Submit message to non-resource recipients
 
1287
         */
 
1288
        // Set BusyStatus to olTentative (1)
 
1289
        // Set MeetingStatus to olMeetingReceived
 
1290
        // Set ResponseStatus to olResponseNotResponded
 
1291
 
 
1292
        /**
 
1293
         * While sending recurrence meeting exceptions are not send as attachments
 
1294
         * because first all exceptions are send and then recurrence meeting is sent.
 
1295
         */
 
1296
        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
 
1297
            // Book resource
 
1298
            $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
 
1299
 
 
1300
            if (!$this->errorSetResource) {
 
1301
                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
 
1302
 
 
1303
                // First send meetingrequest for recurring item
 
1304
                $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
 
1305
 
 
1306
                // Then send all meeting request for all exceptions
 
1307
                $exceptions = $recurr->getAllExceptions();
 
1308
                if ($exceptions) {
 
1309
                    foreach($exceptions as $exceptionBasedate) {
 
1310
                        $attach = $recurr->getExceptionAttachment($exceptionBasedate);
 
1311
 
 
1312
                        if ($attach) {
 
1313
                            $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
 
1314
                            $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
 
1315
                            mapi_savechanges($attach);
 
1316
                        }
 
1317
                    }
 
1318
                }
 
1319
            }
 
1320
        } else {
 
1321
            // Basedate found, an exception is to be send
 
1322
            if ($basedate) {
 
1323
                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
 
1324
 
 
1325
                if ($cancel) {
 
1326
                    //@TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
 
1327
                    $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
 
1328
                } else {
 
1329
                    $attach = $recurr->getExceptionAttachment($basedate);
 
1330
 
 
1331
                    if ($attach) {
 
1332
                        $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
 
1333
 
 
1334
                        // Book resource for this occurrence
 
1335
                        $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
 
1336
 
 
1337
                        if (!$this->errorSetResource) {
 
1338
                            // Save all previous changes
 
1339
                            mapi_savechanges($this->message);
 
1340
 
 
1341
                            $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
 
1342
                            mapi_savechanges($occurrenceItem);
 
1343
                            mapi_savechanges($attach);
 
1344
                        }
 
1345
                    }
 
1346
                }
 
1347
            } else {
 
1348
                // This is normal meeting
 
1349
                $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
 
1350
 
 
1351
                if (!$this->errorSetResource) {
 
1352
                    $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
 
1353
                }
 
1354
            }
 
1355
        }
 
1356
 
 
1357
        if(isset($this->errorSetResource) && $this->errorSetResource){
 
1358
            return Array(
 
1359
                'error' => $this->errorSetResource,
 
1360
                'displayname' => $this->recipientDisplayname
 
1361
            );
 
1362
        }else{
 
1363
            return true;
 
1364
        }
 
1365
    }
 
1366
 
 
1367
 
 
1368
    function getFreeBusyInfo($entryID,$start,$end)
 
1369
    {
 
1370
        $result = array();
 
1371
        $fbsupport = mapi_freebusysupport_open($this->session);
 
1372
 
 
1373
        if(mapi_last_hresult() != NOERROR) {
 
1374
            if(function_exists("dump")) {
 
1375
                dump("Error in opening freebusysupport object.");
 
1376
            }
 
1377
            return $result;
 
1378
        }
 
1379
 
 
1380
        $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, array($entryID));
 
1381
 
 
1382
        if($fbDataArray[0] != NULL){
 
1383
            foreach($fbDataArray as $fbDataUser){
 
1384
                $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
 
1385
                if($rangeuser1 == NULL){
 
1386
                    return $result;
 
1387
                }
 
1388
 
 
1389
                $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
 
1390
                mapi_freebusyenumblock_reset($enumblock);
 
1391
 
 
1392
                while(true){
 
1393
                    $blocks = mapi_freebusyenumblock_next($enumblock, 100);
 
1394
                    if(!$blocks){
 
1395
                        break;
 
1396
                    }
 
1397
                    foreach($blocks as $blockItem){
 
1398
                        $result[] = $blockItem;
 
1399
                    }
 
1400
                }
 
1401
            }
 
1402
        }
 
1403
 
 
1404
        mapi_freebusysupport_close($fbsupport);
 
1405
        return $result;
 
1406
    }
 
1407
 
 
1408
    /**
 
1409
     * Updates the message after an update has been performed (for example,
 
1410
     * changing the time of the meeting). This must be called before re-sending
 
1411
     * the meeting request. You can also call this function instead of 'setMeetingRequest()'
 
1412
     * as it will automatically call setMeetingRequest on this object if it is the first
 
1413
     * call to this function.
 
1414
     */
 
1415
    function updateMeetingRequest($basedate = false)
 
1416
    {
 
1417
        $messageprops = mapi_getprops($this->message, Array($this->proptags['last_updatecounter'], $this->proptags['goid']));
 
1418
 
 
1419
        if(!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
 
1420
            $this->setMeetingRequest($basedate);
 
1421
        } else {
 
1422
            $counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
 
1423
 
 
1424
            // increment value of last_updatecounter, last_updatecounter will be common for recurring series
 
1425
            // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
 
1426
            // this way we can make sure that everytime we will be using a uniwue number for every operation
 
1427
            mapi_setprops($this->message, Array($this->proptags['last_updatecounter'] => $counter));
 
1428
        }
 
1429
    }
 
1430
 
 
1431
    /**
 
1432
     * Returns TRUE if we are the organiser of the meeting.
 
1433
     */
 
1434
    function isLocalOrganiser()
 
1435
    {
 
1436
        if($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
 
1437
            $messageid = $this->getAppointmentEntryID();
 
1438
 
 
1439
            if(!isset($messageid))
 
1440
                return false;
 
1441
 
 
1442
            $message = mapi_msgstore_openentry($this->store, $messageid);
 
1443
 
 
1444
            $messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
 
1445
            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
 
1446
            if ($basedate) {
 
1447
                $recurr = new Recurrence($this->store, $message);
 
1448
                $attach = $recurr->getExceptionAttachment($basedate);
 
1449
                if ($attach) {
 
1450
                    $occurItem = mapi_attach_openobj($attach);
 
1451
                    $occurItemProps = mapi_getprops($occurItem, Array($this->proptags['responsestatus']));
 
1452
                }
 
1453
            }
 
1454
 
 
1455
            $messageprops = mapi_getprops($message, Array($this->proptags['responsestatus']));
 
1456
        }
 
1457
 
 
1458
        /**
 
1459
         * User can send recurring meeting or any occurrences from a recurring appointment so
 
1460
         * to be organizer 'responseStatus' property should be 'olResponseOrganized' on either
 
1461
         * of the recurring item or occurrence item.
 
1462
         */
 
1463
        if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized)
 
1464
            || (isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized))
 
1465
            return true;
 
1466
        else
 
1467
            return false;
 
1468
    }
 
1469
 
 
1470
    /**
 
1471
     * Returns the entryid of the appointment that this message points at. This is
 
1472
     * only used on messages that are not in the calendar.
 
1473
     */
 
1474
    function getAppointmentEntryID()
 
1475
    {
 
1476
        $messageprops = mapi_getprops($this->message, Array($this->proptags['goid2']));
 
1477
 
 
1478
        $goid2 = $messageprops[$this->proptags['goid2']];
 
1479
 
 
1480
        $items = $this->findCalendarItems($goid2);
 
1481
 
 
1482
        if(empty($items))
 
1483
            return;
 
1484
 
 
1485
        // There should be just one item. If there are more, we just take the first one
 
1486
        return $items[0];
 
1487
    }
 
1488
 
 
1489
    /***************************************************************************************************
 
1490
     * Support functions - INTERNAL ONLY
 
1491
     ***************************************************************************************************
 
1492
     */
 
1493
 
 
1494
    /**
 
1495
     * Return the tracking status of a recipient based on the IPM class (passed)
 
1496
     */
 
1497
    function getTrackStatus($class) {
 
1498
        $status = olRecipientTrackStatusNone;
 
1499
        switch($class)
 
1500
        {
 
1501
            case "IPM.Schedule.Meeting.Resp.Pos":
 
1502
                $status = olRecipientTrackStatusAccepted;
 
1503
                break;
 
1504
 
 
1505
            case "IPM.Schedule.Meeting.Resp.Tent":
 
1506
                $status = olRecipientTrackStatusTentative;
 
1507
                break;
 
1508
 
 
1509
            case "IPM.Schedule.Meeting.Resp.Neg":
 
1510
                $status = olRecipientTrackStatusDeclined;
 
1511
                break;
 
1512
        }
 
1513
        return $status;
 
1514
    }
 
1515
 
 
1516
    function openParentFolder() {
 
1517
        $messageprops = mapi_getprops($this->message, Array(PR_PARENT_ENTRYID));
 
1518
 
 
1519
        $parentfolder = mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
 
1520
        return $parentfolder;
 
1521
    }
 
1522
 
 
1523
    function openDefaultCalendar() {
 
1524
        return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
 
1525
    }
 
1526
 
 
1527
    function openDefaultOutbox($store=false) {
 
1528
        return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
 
1529
    }
 
1530
 
 
1531
    function openDefaultWastebasket() {
 
1532
        return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
 
1533
    }
 
1534
 
 
1535
    function getDefaultWastebasketEntryID() {
 
1536
        return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
 
1537
    }
 
1538
 
 
1539
    function getDefaultSentmailEntryID($store=false) {
 
1540
        return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
 
1541
    }
 
1542
 
 
1543
    function getDefaultFolderEntryID($prop) {
 
1544
        try {
 
1545
            $inbox = mapi_msgstore_getreceivefolder($this->store);
 
1546
        } catch (MAPIException $e) {
 
1547
            // public store doesn't support this method
 
1548
            if($e->getCode() == MAPI_E_NO_SUPPORT) {
 
1549
                // don't propogate this error to parent handlers, if store doesn't support it
 
1550
                $e->setHandled();
 
1551
                return;
 
1552
            }
 
1553
        }
 
1554
 
 
1555
        $inboxprops = mapi_getprops($inbox, Array($prop));
 
1556
        if(!isset($inboxprops[$prop]))
 
1557
            return;
 
1558
 
 
1559
        return $inboxprops[$prop];
 
1560
    }
 
1561
 
 
1562
    function openDefaultFolder($prop) {
 
1563
        $entryid = $this->getDefaultFolderEntryID($prop);
 
1564
        $folder = mapi_msgstore_openentry($this->store, $entryid);
 
1565
 
 
1566
        return $folder;
 
1567
    }
 
1568
 
 
1569
    function getBaseEntryID($prop, $store=false) {
 
1570
        $storeprops = mapi_getprops( (($store)?$store:$this->store) , Array($prop));
 
1571
        if(!isset($storeprops[$prop]))
 
1572
            return;
 
1573
 
 
1574
        return $storeprops[$prop];
 
1575
    }
 
1576
 
 
1577
    function openBaseFolder($prop, $store=false) {
 
1578
        $entryid = $this->getBaseEntryID($prop, $store);
 
1579
        $folder = mapi_msgstore_openentry( (($store)?$store:$this->store) , $entryid);
 
1580
 
 
1581
        return $folder;
 
1582
    }
 
1583
    /**
 
1584
     * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
 
1585
     *@param integer $status response status of attendee
 
1586
     *@param integer $proposalStartTime proposed starttime by attendee
 
1587
     *@param integer $proposalEndTime proposed endtime by attendee
 
1588
     *@param integer $basedate date of occurrence which attendee has responded
 
1589
     */
 
1590
    function createResponse($status, $proposalStartTime=false, $proposalEndTime=false, $body=false, $store, $basedate = false, $calFolder) {
 
1591
        $messageprops = mapi_getprops($this->message, Array(PR_SENT_REPRESENTING_ENTRYID,
 
1592
                                                            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
 
1593
                                                            PR_SENT_REPRESENTING_ADDRTYPE,
 
1594
                                                            PR_SENT_REPRESENTING_NAME,
 
1595
                                                            $this->proptags['goid'],
 
1596
                                                            $this->proptags['goid2'],
 
1597
                                                            $this->proptags['location'],
 
1598
                                                            $this->proptags['startdate'],
 
1599
                                                            $this->proptags['duedate'],
 
1600
                                                            $this->proptags['recurring'],
 
1601
                                                            $this->proptags['recurring_pattern'],
 
1602
                                                            $this->proptags['recurrence_data'],
 
1603
                                                            $this->proptags['timezone_data'],
 
1604
                                                            $this->proptags['timezone'],
 
1605
                                                            $this->proptags['updatecounter'],
 
1606
                                                            PR_SUBJECT,
 
1607
                                                            PR_MESSAGE_CLASS,
 
1608
                                                            PR_OWNER_APPT_ID,
 
1609
                                                            $this->proptags['is_exception']
 
1610
                                    ));
 
1611
 
 
1612
        if ($basedate && $messageprops[PR_MESSAGE_CLASS] != "IPM.Schedule.Meeting.Request" ){
 
1613
            // we are creating response from a recurring calendar item object
 
1614
            // We found basedate,so opened occurrence and get properties.
 
1615
            $recurr = new Recurrence($store, $this->message);
 
1616
            $exception = $recurr->getExceptionAttachment($basedate);
 
1617
 
 
1618
            if ($exception) {
 
1619
                // Exception found, Now retrieve properties
 
1620
                $imessage = mapi_attach_openobj($exception, 0);
 
1621
                $imsgprops = mapi_getprops($imessage);
 
1622
 
 
1623
                // If location is provided, copy it to the response
 
1624
                if (isset($imsgprops[$this->proptags['location']])) {
 
1625
                    $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
 
1626
                }
 
1627
 
 
1628
                // Update $messageprops with timings of occurrence
 
1629
                $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
 
1630
                $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
 
1631
 
 
1632
                // Meeting related properties
 
1633
                $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
 
1634
                $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
 
1635
                $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
 
1636
            } else {
 
1637
                // Exceptions is deleted.
 
1638
                // Update $messageprops with timings of occurrence
 
1639
                $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
 
1640
                $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
 
1641
 
 
1642
                $props[$this->proptags['meetingstatus']] = olNonMeeting;
 
1643
                $props[$this->proptags['responsestatus']] = olResponseNone;
 
1644
            }
 
1645
 
 
1646
            $props[$this->proptags['recurring']] = false;
 
1647
            $props[$this->proptags['is_exception']] = true;
 
1648
        } else {
 
1649
            // we are creating a response from meeting request mail (it could be recurring or non-recurring)
 
1650
            // Send all recurrence info in response, if this is a recurrence meeting.
 
1651
            $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
 
1652
            $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
 
1653
            if ($isRecurring || $isException) {
 
1654
                if($isRecurring) {
 
1655
                    $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
 
1656
                }
 
1657
                if($isException) {
 
1658
                    $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
 
1659
                }
 
1660
                $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
 
1661
 
 
1662
                $calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
 
1663
                $recurr = new Recurrence($store, $calendaritem);
 
1664
            }
 
1665
        }
 
1666
 
 
1667
        // we are sending a response for recurring meeting request (or exception), so set some required properties
 
1668
        if(isset($recurr) && $recurr) {
 
1669
            if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
 
1670
                $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
 
1671
            }
 
1672
 
 
1673
            if(!empty($messageprops[$this->proptags['recurrence_data']])) {
 
1674
                $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
 
1675
            }
 
1676
 
 
1677
            $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
 
1678
            $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
 
1679
 
 
1680
            $this->generateRecurDates($recurr, $messageprops, $props);
 
1681
        }
 
1682
 
 
1683
        // Create a response message
 
1684
        $recip = Array();
 
1685
        $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
 
1686
        $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
 
1687
        $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
 
1688
        $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
 
1689
        $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
 
1690
 
 
1691
        switch($status) {
 
1692
            case olResponseAccepted:
 
1693
                $classpostfix = "Pos";
 
1694
                $subjectprefix = _("Accepted");
 
1695
                break;
 
1696
            case olResponseDeclined:
 
1697
                $classpostfix = "Neg";
 
1698
                $subjectprefix = _("Declined");
 
1699
                break;
 
1700
            case olResponseTentative:
 
1701
                $classpostfix = "Tent";
 
1702
                $subjectprefix = _("Tentatively accepted");
 
1703
                break;
 
1704
        }
 
1705
 
 
1706
        if($proposalStartTime && $proposalEndTime){
 
1707
            // if attendee has proposed new time then change subject prefix
 
1708
            $subjectprefix = _("New Time Proposed");
 
1709
        }
 
1710
 
 
1711
        $props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
 
1712
 
 
1713
        $props[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Resp." . $classpostfix;
 
1714
        if(isset($messageprops[PR_OWNER_APPT_ID]))
 
1715
            $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
 
1716
 
 
1717
        // Set GLOBALID AND CLEANGLOBALID, if exception then also set basedate into GLOBALID(0x3).
 
1718
        $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
 
1719
        $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
 
1720
        $props[$this->proptags['updatecounter']] = $messageprops[$this->proptags['updatecounter']];
 
1721
 
 
1722
        // get the default store, in which we have to store the accepted email by delegate or normal user.
 
1723
        $defaultStore = $this->openDefaultStore();
 
1724
        $props[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($defaultStore);
 
1725
 
 
1726
        if($proposalStartTime && $proposalEndTime){
 
1727
            $props[$this->proptags['proposed_start_whole']] = $proposalStartTime;
 
1728
            $props[$this->proptags['proposed_end_whole']] = $proposalEndTime;
 
1729
            $props[$this->proptags['proposed_duration']] = round($proposalEndTime - $proposalStartTime)/60;
 
1730
            $props[$this->proptags['counter_proposal']] = true;
 
1731
        }
 
1732
 
 
1733
        //Set body message in Appointment
 
1734
        if(isset($body)) {
 
1735
            $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
 
1736
        }
 
1737
 
 
1738
        // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
 
1739
        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
 
1740
        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
 
1741
 
 
1742
        // Set startdate and duedate in response mail.
 
1743
        $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
 
1744
        $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
 
1745
 
 
1746
        // responselocation is used in the UI in Outlook on the response message
 
1747
        if (isset($messageprops[$this->proptags['location']])) {
 
1748
            $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
 
1749
            $props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
 
1750
        }
 
1751
 
 
1752
        // check if $store is set and it is not equal to $defaultStore (means its the delegation case)
 
1753
        if(isset($store) && isset($defaultStore)) {
 
1754
            $storeProps = mapi_getprops($store, array(PR_ENTRYID));
 
1755
            $defaultStoreProps = mapi_getprops($defaultStore, array(PR_ENTRYID));
 
1756
 
 
1757
            if($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]){
 
1758
                // get the properties of the other user (for which the logged in user is a delegate).
 
1759
                $storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
 
1760
                $addrbook = mapi_openaddressbook($this->session);
 
1761
                $addrbookitem = mapi_ab_openentry($addrbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
 
1762
                $addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
 
1763
 
 
1764
                // setting the following properties will ensure that the delegation part of message.
 
1765
                $props[PR_SENT_REPRESENTING_ENTRYID] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
 
1766
                $props[PR_SENT_REPRESENTING_NAME] = $addrbookitemprops[PR_DISPLAY_NAME];
 
1767
                $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $addrbookitemprops[PR_EMAIL_ADDRESS];
 
1768
                $props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
 
1769
 
 
1770
                // get the properties of default store and set it accordingly
 
1771
                $defaultStoreProps = mapi_getprops($defaultStore, array(PR_MAILBOX_OWNER_ENTRYID));
 
1772
                $addrbookitem = mapi_ab_openentry($addrbook, $defaultStoreProps[PR_MAILBOX_OWNER_ENTRYID]);
 
1773
                $addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
 
1774
 
 
1775
                // set the following properties will ensure the sender's details, which will be the default user in this case.
 
1776
                //the function returns array($name, $emailaddr, $addrtype, $entryid, $searchkey);
 
1777
                $defaultUserDetails = $this->getOwnerAddress($defaultStore);
 
1778
                $props[PR_SENDER_ENTRYID] = $defaultUserDetails[3];
 
1779
                $props[PR_SENDER_EMAIL_ADDRESS] = $defaultUserDetails[1];
 
1780
                $props[PR_SENDER_NAME] = $defaultUserDetails[0];
 
1781
                $props[PR_SENDER_ADDRTYPE] = $defaultUserDetails[2];
 
1782
            }
 
1783
        }
 
1784
 
 
1785
        // pass the default store to get the required store.
 
1786
        $outbox = $this->openDefaultOutbox($defaultStore);
 
1787
 
 
1788
        $message = mapi_folder_createmessage($outbox);
 
1789
        mapi_setprops($message, $props);
 
1790
        mapi_message_modifyrecipients($message, MODRECIP_ADD, Array($recip));
 
1791
        mapi_message_savechanges($message);
 
1792
        mapi_message_submitmessage($message);
 
1793
    }
 
1794
 
 
1795
    /**
 
1796
     * Function which finds items in calendar based on specified parameters.
 
1797
     *@param binary $goid GlobalID(0x3) of item
 
1798
     *@param resource $calendar MAPI_folder of user
 
1799
     *@param boolean $use_cleanGlobalID if true then search should be performed on cleanGlobalID(0x23) else globalID(0x3)
 
1800
     */
 
1801
    function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
 
1802
        if(!$calendar) {
 
1803
            // Open the Calendar
 
1804
            $calendar = $this->openDefaultCalendar();
 
1805
        }
 
1806
 
 
1807
        // Find the item by restricting all items to the correct ID
 
1808
        $restrict = Array(RES_AND, Array());
 
1809
 
 
1810
        array_push($restrict[1], Array(RES_PROPERTY,
 
1811
                                                    Array(RELOP => RELOP_EQ,
 
1812
                                                          ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
 
1813
                                                          VALUE => $goid
 
1814
                                                    )
 
1815
                                    ));
 
1816
 
 
1817
        $calendarcontents = mapi_folder_getcontentstable($calendar);
 
1818
 
 
1819
        $rows = mapi_table_queryallrows($calendarcontents, Array(PR_ENTRYID), $restrict);
 
1820
 
 
1821
        if(empty($rows))
 
1822
            return;
 
1823
 
 
1824
        $calendaritems = Array();
 
1825
 
 
1826
        // In principle, there should only be one row, but we'll handle them all just in case
 
1827
        foreach($rows as $row) {
 
1828
            $calendaritems[] = $row[PR_ENTRYID];
 
1829
        }
 
1830
 
 
1831
        return $calendaritems;
 
1832
    }
 
1833
 
 
1834
    // Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
 
1835
    // same SMTP address when converted to SMTP
 
1836
    function compareABEntryIDs($entryid1, $entryid2) {
 
1837
        // If the session was not passed, just do a 'normal' compare.
 
1838
        if(!$this->session)
 
1839
            return $entryid1 == $entryid2;
 
1840
 
 
1841
        $smtp1 = $this->getSMTPAddress($entryid1);
 
1842
        $smtp2 = $this->getSMTPAddress($entryid2);
 
1843
 
 
1844
        if($smtp1 == $smtp2)
 
1845
            return true;
 
1846
        else
 
1847
            return false;
 
1848
    }
 
1849
 
 
1850
    // Gets the SMTP address of the passed addressbook entryid
 
1851
    function getSMTPAddress($entryid) {
 
1852
        if(!$this->session)
 
1853
            return false;
 
1854
 
 
1855
        $ab = mapi_openaddressbook($this->session);
 
1856
 
 
1857
        $abitem = mapi_ab_openentry($ab, $entryid);
 
1858
 
 
1859
        if(!$abitem)
 
1860
            return "";
 
1861
 
 
1862
        $props = mapi_getprops($abitem, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
 
1863
 
 
1864
        if($props[PR_ADDRTYPE] == "SMTP") {
 
1865
            return $props[PR_EMAIL_ADDRESS];
 
1866
        }
 
1867
        else return $props[PR_SMTP_ADDRESS];
 
1868
    }
 
1869
 
 
1870
    /**
 
1871
     * Gets the properties associated with the owner of the passed store:
 
1872
     * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY
 
1873
     *
 
1874
     * @param $store message store
 
1875
     * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
 
1876
     * not used when passed store is public store. for public store we are always returning logged in user's info.
 
1877
     * @return properties of logged in user in an array in sequence of display_name, email address, address tyep,
 
1878
     * entryid and search key.
 
1879
     */
 
1880
    function getOwnerAddress($store, $fallbackToLoggedInUser = true)
 
1881
    {
 
1882
        if(!$this->session)
 
1883
            return false;
 
1884
 
 
1885
        $storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID));
 
1886
 
 
1887
        $ownerEntryId = false;
 
1888
        if(isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
 
1889
            $ownerEntryId = $storeProps[PR_USER_ENTRYID];
 
1890
        }
 
1891
 
 
1892
        if(isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
 
1893
            $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
 
1894
        }
 
1895
 
 
1896
        if($ownerEntryId) {
 
1897
            $ab = mapi_openaddressbook($this->session);
 
1898
 
 
1899
            $zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
 
1900
            if(!$zarafaUser)
 
1901
                return false;
 
1902
 
 
1903
            $ownerProps = mapi_getprops($zarafaUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
 
1904
 
 
1905
            $addrType = $ownerProps[PR_ADDRTYPE];
 
1906
            $name = $ownerProps[PR_DISPLAY_NAME];
 
1907
            $emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
 
1908
            $searchKey = strtoupper($addrType) . ":" . strtoupper($emailAddr);
 
1909
            $entryId = $ownerEntryId;
 
1910
 
 
1911
            return array($name, $emailAddr, $addrType, $entryId, $searchKey);
 
1912
        }
 
1913
 
 
1914
        return false;
 
1915
    }
 
1916
 
 
1917
    // Opens this session's default message store
 
1918
    function openDefaultStore()
 
1919
    {
 
1920
        $storestable = mapi_getmsgstorestable($this->session);
 
1921
        $rows = mapi_table_queryallrows($storestable, array(PR_ENTRYID, PR_DEFAULT_STORE));
 
1922
        $entry = false;
 
1923
 
 
1924
        foreach($rows as $row) {
 
1925
            if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
 
1926
                $entryid = $row[PR_ENTRYID];
 
1927
                break;
 
1928
            }
 
1929
        }
 
1930
 
 
1931
        if(!$entryid)
 
1932
            return false;
 
1933
 
 
1934
        return mapi_openmsgstore($this->session, $entryid);
 
1935
    }
 
1936
    /**
 
1937
     *  Function which adds organizer to recipient list which is passed.
 
1938
     *  This function also checks if it has organizer.
 
1939
     *
 
1940
     * @param array $messageProps message properties
 
1941
     * @param array $recipients    recipients list of message.
 
1942
     * @param boolean $isException true if we are processing recipient of exception
 
1943
     */
 
1944
    function addOrganizer($messageProps, &$recipients, $isException = false){
 
1945
 
 
1946
        $hasOrganizer = false;
 
1947
        // Check if meeting already has an organizer.
 
1948
        foreach ($recipients as $key => $recipient){
 
1949
            if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
 
1950
                $hasOrganizer = true;
 
1951
            } else if ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])){
 
1952
                // Recipients for an occurrence
 
1953
                $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
 
1954
            }
 
1955
        }
 
1956
 
 
1957
        if (!$hasOrganizer){
 
1958
            // Create organizer.
 
1959
            $organizer = array();
 
1960
            $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
 
1961
            $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
 
1962
            $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
 
1963
            $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
 
1964
            $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
 
1965
            $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
 
1966
            $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
 
1967
            $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
 
1968
 
 
1969
            // Add organizer to recipients list.
 
1970
            array_unshift($recipients, $organizer);
 
1971
        }
 
1972
    }
 
1973
 
 
1974
    /**
 
1975
     * Function adds recipients in recips array from the string.
 
1976
     *
 
1977
     * @param array $recips recipient array.
 
1978
     * @param string $recipString recipient string attendees.
 
1979
     * @param int $type type of the recipient, MAPI_TO/MAPI_CC.
 
1980
     */
 
1981
    function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO)
 
1982
    {
 
1983
        $extraRecipient = array();
 
1984
        $recipArray = explode(";", $recipString);
 
1985
 
 
1986
        foreach($recipArray as $recip) {
 
1987
            $recip = trim($recip);
 
1988
            if (!empty($recip)) {
 
1989
                $extraRecipient[PR_RECIPIENT_TYPE] = $recipType;
 
1990
                $extraRecipient[PR_DISPLAY_NAME] = $recip;
 
1991
                array_push($recips, $extraRecipient);
 
1992
            }
 
1993
        }
 
1994
 
 
1995
    }
 
1996
 
 
1997
    /**
 
1998
     * Function which removes an exception/occurrence from recurrencing meeting
 
1999
     * when a meeting cancellation of an occurrence is processed.
 
2000
     *@param string $basedate basedate of an occurrence
 
2001
     *@param resource $message recurring item from which occurrence has to be deleted
 
2002
     *@param resource $store MAPI_MSG_Store which contains the item
 
2003
     */
 
2004
    function doRemoveExceptionFromCalendar($basedate, $message, $store)
 
2005
    {
 
2006
        $recurr = new Recurrence($store, $message);
 
2007
        $recurr->createException(array(), $basedate, true);
 
2008
        mapi_savechanges($message);
 
2009
    }
 
2010
 
 
2011
    /**
 
2012
     * Function which returns basedate of an changed occurrance from globalID of meeting request.
 
2013
     *@param binary $goid globalID
 
2014
     *@return boolean true if basedate is found else false it not found
 
2015
     */
 
2016
    function getBasedateFromGlobalID($goid)
 
2017
    {
 
2018
        $hexguid = bin2hex($goid);
 
2019
        $hexbase = substr($hexguid, 32, 8);
 
2020
        $day = hexdec(substr($hexbase, 6, 2));
 
2021
        $month = hexdec(substr($hexbase, 4, 2));
 
2022
        $year = hexdec(substr($hexbase, 0, 4));
 
2023
 
 
2024
        if ($day && $month && $year)
 
2025
            return gmmktime(0, 0, 0, $month, $day, $year);
 
2026
        else
 
2027
            return false;
 
2028
    }
 
2029
 
 
2030
    /**
 
2031
     * Function which sets basedate in globalID of changed occurrance which is to be send.
 
2032
     *@param binary $goid globalID
 
2033
     *@param string basedate of changed occurrance
 
2034
     *@return binary globalID with basedate in it
 
2035
     */
 
2036
    function setBasedateInGlobalID($goid, $basedate = false)
 
2037
    {
 
2038
        $hexguid = bin2hex($goid);
 
2039
        $year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
 
2040
        $month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
 
2041
        $day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
 
2042
 
 
2043
        return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
 
2044
    }
 
2045
    /**
 
2046
     * Function which replaces attachments with copy_from in copy_to.
 
2047
     *@param resource $copy_from MAPI_message from which attachments are to be copied.
 
2048
     *@param resource $copy_to MAPI_message to which attachment are to be copied.
 
2049
     *@param boolean $copyExceptions if true then all exceptions should also be sent as attachments
 
2050
     */
 
2051
    function replaceAttachments($copy_from, $copy_to, $copyExceptions = true)
 
2052
    {
 
2053
        /* remove all old attachments */
 
2054
        $attachmentTable = mapi_message_getattachmenttable($copy_to);
 
2055
        if($attachmentTable) {
 
2056
            $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
 
2057
 
 
2058
            foreach($attachments as $attach_props){
 
2059
                /* remove exceptions too? */
 
2060
                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
 
2061
                    continue;
 
2062
                mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
 
2063
            }
 
2064
        }
 
2065
        $attachmentTable = false;
 
2066
 
 
2067
        /* copy new attachments */
 
2068
        $attachmentTable = mapi_message_getattachmenttable($copy_from);
 
2069
        if($attachmentTable) {
 
2070
            $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
 
2071
 
 
2072
            foreach($attachments as $attach_props){
 
2073
                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
 
2074
                    continue;
 
2075
 
 
2076
                $attach_old = mapi_message_openattach($copy_from, (int) $attach_props[PR_ATTACH_NUM]);
 
2077
                $attach_newResourceMsg = mapi_message_createattach($copy_to);
 
2078
                mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
 
2079
                mapi_savechanges($attach_newResourceMsg);
 
2080
            }
 
2081
        }
 
2082
    }
 
2083
    /**
 
2084
     * Function which replaces recipients in copy_to with recipients from copy_from.
 
2085
     *@param resource $copy_from MAPI_message from which recipients are to be copied.
 
2086
     *@param resource $copy_to MAPI_message to which recipients are to be copied.
 
2087
     */
 
2088
    function replaceRecipients($copy_from, $copy_to, $isDelegate = false)
 
2089
    {
 
2090
        $recipienttable = mapi_message_getrecipienttable($copy_from);
 
2091
 
 
2092
        // If delegate, then do not add the delegate in recipients
 
2093
        if ($isDelegate) {
 
2094
            $delegate = mapi_getprops($copy_from, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
 
2095
            $res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
 
2096
            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $res);
 
2097
        } else {
 
2098
            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
 
2099
        }
 
2100
 
 
2101
        $copy_to_recipientTable = mapi_message_getrecipienttable($copy_to);
 
2102
        $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
 
2103
        foreach($copy_to_recipientRows as $recipient) {
 
2104
            mapi_message_modifyrecipients($copy_to, MODRECIP_REMOVE, array($recipient));
 
2105
        }
 
2106
 
 
2107
        mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
 
2108
    }
 
2109
    /**
 
2110
     * Function creates meeting item in resource's calendar.
 
2111
     *@param resource $message MAPI_message which is to create in resource's calendar
 
2112
     *@param boolean $cancel cancel meeting
 
2113
     *@param string $prefix prefix for subject of meeting
 
2114
     */
 
2115
    function bookResources($message, $cancel, $prefix, $basedate = false)
 
2116
    {
 
2117
        if(!$this->enableDirectBooking)
 
2118
            return array();
 
2119
 
 
2120
        // Get the properties of the message
 
2121
        $messageprops = mapi_getprops($message);
 
2122
 
 
2123
        if ($basedate) {
 
2124
            $recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID));
 
2125
 
 
2126
            $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
 
2127
            $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
 
2128
 
 
2129
            // Delete properties which are not needed.
 
2130
            $deleteProps = array($this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD);
 
2131
            foreach ($deleteProps as $propID) {
 
2132
                if (isset($messageprops[$propID])) {
 
2133
                    unset($messageprops[$propID]);
 
2134
                }
 
2135
            }
 
2136
 
 
2137
            if (isset($messageprops[$this->proptags['recurring']])) $messageprops[$this->proptags['recurring']] = false;
 
2138
 
 
2139
            // Set Outlook properties
 
2140
            $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
 
2141
            $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
 
2142
            $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
 
2143
            $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
 
2144
            $messageprops[$this->proptags['attendee_critical_change']] = time();
 
2145
            $messageprops[$this->proptags['owner_critical_change']] = time();
 
2146
        }
 
2147
 
 
2148
        // Get resource recipients
 
2149
        $getResourcesRestriction = Array(RES_AND,
 
2150
            Array(Array(RES_PROPERTY,
 
2151
                Array(RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
 
2152
                    ULPROPTAG => PR_RECIPIENT_TYPE,
 
2153
                    VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
 
2154
                )
 
2155
            ))
 
2156
        );
 
2157
        $recipienttable = mapi_message_getrecipienttable($message);
 
2158
        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
 
2159
 
 
2160
        $this->errorSetResource = false;
 
2161
        $resourceRecipData = Array();
 
2162
 
 
2163
        // Put appointment into store resource users
 
2164
        $i = 0;
 
2165
        $len = count($resourceRecipients);
 
2166
        while(!$this->errorSetResource && $i < $len){
 
2167
            $request = array(array(PR_DISPLAY_NAME => $resourceRecipients[$i][PR_DISPLAY_NAME]));
 
2168
            $ab = mapi_openaddressbook($this->session);
 
2169
            $ret = mapi_ab_resolvename($ab, $request, EMS_AB_ADDRESS_LOOKUP);
 
2170
            $result = mapi_last_hresult();
 
2171
            if ($result == NOERROR){
 
2172
                $result = $ret[0][PR_ENTRYID];
 
2173
            }
 
2174
            $resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
 
2175
            $resourceABEntryID = $ret[0][PR_ENTRYID];
 
2176
 
 
2177
            // Get StoreEntryID by username
 
2178
            $user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
 
2179
 
 
2180
            // Open store of the user
 
2181
            $userStore = mapi_openmsgstore($this->session, $user_entryid);
 
2182
            // Open root folder
 
2183
            $userRoot = mapi_msgstore_openentry($userStore, null);
 
2184
            // Get calendar entryID
 
2185
            $userRootProps = mapi_getprops($userRoot, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
 
2186
 
 
2187
            // Open Calendar folder   [check hresult==0]
 
2188
            $accessToFolder = false;
 
2189
            try {
 
2190
                $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
 
2191
                if($calFolder){
 
2192
                    $calFolderProps = mapi_getProps($calFolder, Array(PR_ACCESS));
 
2193
                    if(($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0){
 
2194
                        $accessToFolder = true;
 
2195
                    }
 
2196
                }
 
2197
            } catch (MAPIException $e) {
 
2198
                $e->setHandled();
 
2199
                $this->errorSetResource = 1; // No access
 
2200
            }
 
2201
 
 
2202
            if($accessToFolder) {
 
2203
                /**
 
2204
                 * Get the LocalFreebusy message that contains the properties that
 
2205
                 * are set to accept or decline resource meeting requests
 
2206
                 */
 
2207
                // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
 
2208
                $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
 
2209
                if($localFreebusyMsg){
 
2210
                    $props = mapi_getprops($localFreebusyMsg, array(PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS));
 
2211
 
 
2212
                    $acceptMeetingRequests = ($props[PR_PROCESS_MEETING_REQUESTS])?1:0;
 
2213
                    $declineRecurringMeetingRequests = ($props[PR_DECLINE_RECURRING_MEETING_REQUESTS])?1:0;
 
2214
                    $declineConflictingMeetingRequests = ($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS])?1:0;
 
2215
                    if(!$acceptMeetingRequests){
 
2216
                        /**
 
2217
                         * When a resource has not been set to automatically accept meeting requests,
 
2218
                         * the meeting request has to be sent to him rather than being put directly into
 
2219
                         * his calendar. No error should be returned.
 
2220
                         */
 
2221
                        //$errorSetResource = 2;
 
2222
                        $this->nonAcceptingResources[] = $resourceRecipients[$i];
 
2223
                    }else{
 
2224
                        if($declineRecurringMeetingRequests && !$cancel){
 
2225
                            // Check if appointment is recurring
 
2226
                            if($messageprops[ $this->proptags['recurring'] ]){
 
2227
                                $this->errorSetResource = 3;
 
2228
                            }
 
2229
                        }
 
2230
                        if($declineConflictingMeetingRequests && !$cancel){
 
2231
                            // Check for conflicting items
 
2232
                            $conflicting = false;
 
2233
 
 
2234
                            // Open the calendar
 
2235
                            $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
 
2236
 
 
2237
                            if($calFolder) {
 
2238
                                if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops))
 
2239
                                    $conflicting = true;
 
2240
                            } else {
 
2241
                                $this->errorSetResource = 1; // No access
 
2242
                            }
 
2243
 
 
2244
                            if($conflicting){
 
2245
                                $this->errorSetResource = 4; // Conflict
 
2246
                            }
 
2247
                        }
 
2248
                    }
 
2249
                }
 
2250
            }
 
2251
 
 
2252
            if(!$this->errorSetResource && $accessToFolder){
 
2253
                /**
 
2254
                 * First search on GlobalID(0x3)
 
2255
                 * 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.
 
2256
                 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesnt matter if search is based on GlobalID.
 
2257
                 */
 
2258
                $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
 
2259
 
 
2260
                /**
 
2261
                 * If no entry is found then
 
2262
                 * 1) Resource doesnt have meeting in Calendar. Seriously!!
 
2263
                 * OR
 
2264
                 * 2) We were looking for occurrence item but Resource has whole series
 
2265
                 */
 
2266
                if(empty($rows)){
 
2267
                    /**
 
2268
                     * Now search on CleanGlobalID(0x23) WHY???
 
2269
                     * Because we are looking recurring item
 
2270
                     *
 
2271
                     * Possible results of this search
 
2272
                     * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
 
2273
                     * 2) If Resource was booked for whole series then it should return series.
 
2274
                     */
 
2275
                    $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
 
2276
 
 
2277
                    $newResourceMsg = false;
 
2278
                    if (!empty($rows)) {
 
2279
                        // Since we are looking for recurring item, open every result and check for 'recurring' property.
 
2280
                        foreach($rows as $row) {
 
2281
                            $ResourceMsg = mapi_msgstore_openentry($userStore, $row);
 
2282
                            $ResourceMsgProps = mapi_getprops($ResourceMsg, array($this->proptags['recurring']));
 
2283
 
 
2284
                            if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
 
2285
                                $newResourceMsg = $ResourceMsg;
 
2286
                                break;
 
2287
                            }
 
2288
                        }
 
2289
                    }
 
2290
 
 
2291
                    // Still no results found. I giveup, create new message.
 
2292
                    if (!$newResourceMsg)
 
2293
                        $newResourceMsg = mapi_folder_createmessage($calFolder);
 
2294
                }else{
 
2295
                    $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
 
2296
                }
 
2297
 
 
2298
                // Prefix the subject if needed
 
2299
                if($prefix && isset($messageprops[PR_SUBJECT])) {
 
2300
                    $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
 
2301
                }
 
2302
 
 
2303
                // Set status to cancelled if needed
 
2304
                $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
 
2305
                if($cancel) {
 
2306
                    $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
 
2307
                    $messageprops[$this->proptags['busystatus']] = fbFree; // Free
 
2308
                } else {
 
2309
                    $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
 
2310
                }
 
2311
                $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource autmatically accepts the appointment
 
2312
 
 
2313
                $messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
 
2314
 
 
2315
                // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
 
2316
                // confuses the Zarafa webaccess
 
2317
                $messageprops[PR_ICON_INDEX] = null;
 
2318
                $messageprops[PR_RESPONSE_REQUESTED] = true;
 
2319
 
 
2320
                $addrinfo = $this->getOwnerAddress($this->store);
 
2321
 
 
2322
                if($addrinfo) {
 
2323
                    list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
 
2324
 
 
2325
                    $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
 
2326
                    $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
 
2327
                    $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
 
2328
                    $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
 
2329
                    $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
 
2330
 
 
2331
                    $messageprops[$this->proptags['apptreplyname']] = $ownername;
 
2332
                    $messageprops[$this->proptags['replytime']] = time();
 
2333
                }
 
2334
 
 
2335
                if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
 
2336
                    $recurr = new Recurrence($userStore, $newResourceMsg);
 
2337
 
 
2338
                    // Copy recipients list
 
2339
                    $reciptable = mapi_message_getrecipienttable($message);
 
2340
                    $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
 
2341
                    // add owner to recipient table
 
2342
                    $this->addOrganizer($messageprops, $recips, true);
 
2343
 
 
2344
                    // Update occurrence
 
2345
                    if($recurr->isException($basedate))
 
2346
                        $recurr->modifyException($messageprops, $basedate, $recips);
 
2347
                    else
 
2348
                        $recurr->createException($messageprops, $basedate, false, $recips);
 
2349
                } else {
 
2350
 
 
2351
                    mapi_setprops($newResourceMsg, $messageprops);
 
2352
 
 
2353
                    // Copy attachments
 
2354
                    $this->replaceAttachments($message, $newResourceMsg);
 
2355
 
 
2356
                    // Copy all recipients too
 
2357
                    $this->replaceRecipients($message, $newResourceMsg);
 
2358
 
 
2359
                    // Now add organizer also to recipient table
 
2360
                    $recips = Array();
 
2361
                    $this->addOrganizer($messageprops, $recips);
 
2362
                    mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
 
2363
                }
 
2364
 
 
2365
                mapi_savechanges($newResourceMsg);
 
2366
 
 
2367
                $resourceRecipData[] = Array(
 
2368
                    'store' => $userStore,
 
2369
                    'folder' => $calFolder,
 
2370
                    'msg' => $newResourceMsg,
 
2371
                );
 
2372
                $this->includesResources = true;
 
2373
            }else{
 
2374
                /**
 
2375
                 * If no other errors occured and you have no access to the
 
2376
                 * folder of the resource, throw an error=1.
 
2377
                 */
 
2378
                if(!$this->errorSetResource){
 
2379
                    $this->errorSetResource = 1;
 
2380
                }
 
2381
 
 
2382
                for($j = 0, $len = count($resourceRecipData); $j < $len; $j++){
 
2383
                    // Get the EntryID
 
2384
                    $props = mapi_message_getprops($resourceRecipData[$j]['msg']);
 
2385
 
 
2386
                    mapi_folder_deletemessages($resourceRecipData[$j]['folder'], Array($props[PR_ENTRYID]), DELETE_HARD_DELETE);
 
2387
                }
 
2388
                $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
 
2389
            }
 
2390
            $i++;
 
2391
        }
 
2392
 
 
2393
        /**************************************************************
 
2394
         * Set the BCC-recipients (resources) tackstatus to accepted.
 
2395
         */
 
2396
        // Get resource recipients
 
2397
        $getResourcesRestriction = Array(RES_AND,
 
2398
            Array(Array(RES_PROPERTY,
 
2399
                Array(RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
 
2400
                    ULPROPTAG => PR_RECIPIENT_TYPE,
 
2401
                    VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
 
2402
                )
 
2403
            ))
 
2404
        );
 
2405
        $recipienttable = mapi_message_getrecipienttable($message);
 
2406
        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
 
2407
        if(!empty($resourceRecipients)){
 
2408
            // Set Tracking status of resource recipients to olResponseAccepted (3)
 
2409
            for($i = 0, $len = count($resourceRecipients); $i < $len; $i++){
 
2410
                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
 
2411
                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
 
2412
            }
 
2413
            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
 
2414
        }
 
2415
 
 
2416
        // Publish updated free/busy information
 
2417
        if(!$this->errorSetResource){
 
2418
            for($i = 0, $len = count($resourceRecipData); $i < $len; $i++){
 
2419
                $storeProps = mapi_msgstore_getprops($resourceRecipData[$i]['store'], array(PR_MAILBOX_OWNER_ENTRYID));
 
2420
                if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])){
 
2421
                    $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
 
2422
                    $pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
 
2423
                }
 
2424
            }
 
2425
        }
 
2426
 
 
2427
        return $resourceRecipData;
 
2428
    }
 
2429
    /**
 
2430
     * Function which save an exception into recurring item
 
2431
     *
 
2432
     * @param resource $recurringItem reference to MAPI_message of recurring item
 
2433
     * @param resource $occurrenceItem reference to MAPI_message of occurrence
 
2434
     * @param string $basedate basedate of occurrence
 
2435
     * @param boolean $move if true then occurrence item is deleted
 
2436
     * @param boolean $tentative true if user has tentatively accepted it or false if user has accepted it.
 
2437
     * @param boolean $userAction true if user has manually responded to meeting request
 
2438
     * @param resource $store user store
 
2439
     * @param boolean $isDelegate true if delegate is processing this meeting request
 
2440
     */
 
2441
    function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false)
 
2442
    {
 
2443
        $recurr = new Recurrence($store, $recurringItem);
 
2444
 
 
2445
        // Copy properties from meeting request
 
2446
        $exception_props = mapi_getprops($occurrenceItem);
 
2447
 
 
2448
        // Copy recipients list
 
2449
        $reciptable = mapi_message_getrecipienttable($occurrenceItem);
 
2450
        // If delegate, then do not add the delegate in recipients
 
2451
        if ($isDelegate) {
 
2452
            $delegate = mapi_getprops($this->message, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
 
2453
            $res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
 
2454
            $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
 
2455
        } else {
 
2456
            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
 
2457
        }
 
2458
 
 
2459
 
 
2460
        // add owner to recipient table
 
2461
        $this->addOrganizer($exception_props, $recips, true);
 
2462
 
 
2463
        // add delegator to meetings
 
2464
        if ($isDelegate) $this->addDelegator($exception_props, $recips);
 
2465
 
 
2466
        $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
 
2467
        $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
 
2468
        // Set basedate property (ExceptionReplaceTime)
 
2469
 
 
2470
        if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
 
2471
            if($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
 
2472
                $exception_props[$this->proptags['busystatus']] = $tentative;
 
2473
            } else {
 
2474
                $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
 
2475
            }
 
2476
            // we already have intendedbusystatus value in $exception_props so no need to copy it
 
2477
        } else {
 
2478
            $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
 
2479
        }
 
2480
 
 
2481
        if($userAction) {
 
2482
            // if user has responded then set replytime
 
2483
            $exception_props[$this->proptags['replytime']] = time();
 
2484
        }
 
2485
 
 
2486
        if($recurr->isException($basedate))
 
2487
            $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
 
2488
        else
 
2489
            $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
 
2490
 
 
2491
        // Move the occurrenceItem to the waste basket
 
2492
        if ($move) {
 
2493
            $wastebasket = $this->openDefaultWastebasket();
 
2494
            $sourcefolder = mapi_msgstore_openentry($this->store, $exception_props[PR_PARENT_ENTRYID]);
 
2495
            mapi_folder_copymessages($sourcefolder, Array($exception_props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
 
2496
        }
 
2497
 
 
2498
        mapi_savechanges($recurringItem);
 
2499
    }
 
2500
 
 
2501
    /**
 
2502
     * Function which submits meeting request based on arguments passed to it.
 
2503
     *@param resource $message MAPI_message whose meeting request is to be send
 
2504
     *@param boolean $cancel if true send request, else send cancellation
 
2505
     *@param string $prefix subject prefix
 
2506
     *@param integer $basedate basedate for an occurrence
 
2507
     *@param Object $recurObject recurrence object of mr
 
2508
     *@param boolean $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
 
2509
     */
 
2510
    function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false)
 
2511
    {
 
2512
        $newmessageprops = $messageprops = mapi_getprops($this->message);
 
2513
        $new = $this->createOutgoingMessage();
 
2514
 
 
2515
        // Copy the entire message into the new meeting request message
 
2516
        if ($basedate) {
 
2517
            // messageprops contains properties of whole recurring series
 
2518
            // and newmessageprops contains properties of exception item
 
2519
            $newmessageprops = mapi_getprops($message);
 
2520
 
 
2521
            // Ensure that the correct basedate is set in the new message
 
2522
            $newmessageprops[$this->proptags['basedate']] = $basedate;
 
2523
 
 
2524
            // Set isRecurring to false, because this is an exception
 
2525
            $newmessageprops[$this->proptags['recurring']] = false;
 
2526
 
 
2527
            // set LID_IS_EXCEPTION to true
 
2528
            $newmessageprops[$this->proptags['is_exception']] = true;
 
2529
 
 
2530
            // Set to high importance
 
2531
            if($cancel) $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
 
2532
 
 
2533
            // Set startdate and enddate of exception
 
2534
            if ($cancel && $recurObject) {
 
2535
                $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
 
2536
                $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
 
2537
            }
 
2538
 
 
2539
            // Set basedate in guid (0x3)
 
2540
            $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
 
2541
            $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
 
2542
            $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
 
2543
 
 
2544
            // Get deleted recipiets from exception msg
 
2545
            $restriction = Array(RES_AND,
 
2546
                            Array(
 
2547
                                Array(RES_BITMASK,
 
2548
                                    Array(    ULTYPE        =>    BMR_NEZ,
 
2549
                                            ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
 
2550
                                            ULMASK        =>    recipExceptionalDeleted
 
2551
                                    )
 
2552
                                ),
 
2553
                                Array(RES_BITMASK,
 
2554
                                    Array(    ULTYPE        =>    BMR_EQZ,
 
2555
                                            ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
 
2556
                                            ULMASK        =>    recipOrganizer
 
2557
                                    )
 
2558
                                ),
 
2559
                            )
 
2560
            );
 
2561
 
 
2562
            // In direct-booking mode, we don't need to send cancellations to resources
 
2563
            if($this->enableDirectBooking) {
 
2564
                $restriction[1][] = Array(RES_PROPERTY,
 
2565
                                        Array(RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
 
2566
                                            ULPROPTAG => PR_RECIPIENT_TYPE,
 
2567
                                            VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
 
2568
                                        )
 
2569
                                    );
 
2570
            }
 
2571
 
 
2572
            $recipienttable = mapi_message_getrecipienttable($message);
 
2573
            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
 
2574
 
 
2575
            if (!$deletedRecips) {
 
2576
                $deletedRecips = array_merge(array(), $recipients);
 
2577
            } else {
 
2578
                $deletedRecips = array_merge($deletedRecips, $recipients);
 
2579
            }
 
2580
        }
 
2581
 
 
2582
        // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
 
2583
        // confuses the Zarafa webaccess
 
2584
        $newmessageprops[PR_ICON_INDEX] = null;
 
2585
        $newmessageprops[PR_RESPONSE_REQUESTED] = true;
 
2586
 
 
2587
        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
 
2588
        $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
 
2589
        $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
 
2590
 
 
2591
        // Set updatecounter/AppointmentSequenceNumber
 
2592
        // get the value of latest updatecounter for the whole series and use it
 
2593
        $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
 
2594
 
 
2595
        $meetingTimeInfo = $this->getMeetingTimeInfo();
 
2596
 
 
2597
        if($meetingTimeInfo)
 
2598
            $newmessageprops[PR_BODY] = $meetingTimeInfo;
 
2599
 
 
2600
        // Send all recurrence info in mail, if this is a recurrence meeting.
 
2601
        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
 
2602
            if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
 
2603
                $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
 
2604
            }
 
2605
            $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
 
2606
            $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
 
2607
            $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
 
2608
 
 
2609
            if($recurObject) {
 
2610
                $this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
 
2611
            }
 
2612
        }
 
2613
 
 
2614
        if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
 
2615
            unset($newmessageprops[$this->proptags['counter_proposal']]);
 
2616
        }
 
2617
 
 
2618
        // Prefix the subject if needed
 
2619
        if ($prefix && isset($newmessageprops[PR_SUBJECT]))
 
2620
            $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
 
2621
 
 
2622
        mapi_setprops($new, $newmessageprops);
 
2623
 
 
2624
        // Copy attachments
 
2625
        $this->replaceAttachments($message, $new, $copyExceptions);
 
2626
 
 
2627
        // Retrieve only those recipient who should receive this meeting request.
 
2628
        $stripResourcesRestriction = Array(RES_AND,
 
2629
                                Array(
 
2630
                                    Array(RES_BITMASK,
 
2631
                                        Array(    ULTYPE        =>    BMR_EQZ,
 
2632
                                                ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
 
2633
                                                ULMASK        =>    recipExceptionalDeleted
 
2634
                                        )
 
2635
                                    ),
 
2636
                                    Array(RES_BITMASK,
 
2637
                                        Array(    ULTYPE        =>    BMR_EQZ,
 
2638
                                                ULPROPTAG    =>    PR_RECIPIENT_FLAGS,
 
2639
                                                ULMASK        =>    recipOrganizer
 
2640
                                        )
 
2641
                                    ),
 
2642
                                )
 
2643
        );
 
2644
 
 
2645
        // In direct-booking mode, resources do not receive a meeting request
 
2646
        if($this->enableDirectBooking) {
 
2647
            $stripResourcesRestriction[1][] =
 
2648
                                    Array(RES_PROPERTY,
 
2649
                                        Array(RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
 
2650
                                            ULPROPTAG => PR_RECIPIENT_TYPE,
 
2651
                                            VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
 
2652
                                        )
 
2653
                                    );
 
2654
        }
 
2655
 
 
2656
        $recipienttable = mapi_message_getrecipienttable($message);
 
2657
        $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
 
2658
 
 
2659
        if ($basedate && empty($recipients)) {
 
2660
            // Retrieve full list
 
2661
            $recipienttable = mapi_message_getrecipienttable($this->message);
 
2662
            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
 
2663
 
 
2664
            // Save recipients in exceptions
 
2665
            mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
 
2666
 
 
2667
            // Now retrieve only those recipient who should receive this meeting request.
 
2668
            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
 
2669
        }
 
2670
 
 
2671
        //@TODO: handle nonAcceptingResources
 
2672
        /**
 
2673
         * Add resource recipients that did not automatically accept the meeting request.
 
2674
         * (note: meaning that they did not decline the meeting request)
 
2675
         *//*
 
2676
        for($i=0;$i<count($this->nonAcceptingResources);$i++){
 
2677
            $recipients[] = $this->nonAcceptingResources[$i];
 
2678
        }*/
 
2679
 
 
2680
        if(!empty($recipients)) {
 
2681
            // Strip out the sender/"owner" recipient
 
2682
            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
 
2683
 
 
2684
            // Set some properties that are different in the sent request than
 
2685
            // in the item in our calendar
 
2686
 
 
2687
            // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
 
2688
            // should always be fbTentative
 
2689
            $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
 
2690
            $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
 
2691
            $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
 
2692
            $newmessageprops[$this->proptags['attendee_critical_change']] = time();
 
2693
            $newmessageprops[$this->proptags['owner_critical_change']] = time();
 
2694
            $newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
 
2695
 
 
2696
            if ($cancel) {
 
2697
                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
 
2698
                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
 
2699
                $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
 
2700
            } else {
 
2701
                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
 
2702
                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
 
2703
            }
 
2704
 
 
2705
            mapi_setprops($new, $newmessageprops);
 
2706
            mapi_message_savechanges($new);
 
2707
 
 
2708
            // Submit message to non-resource recipients
 
2709
            mapi_message_submitmessage($new);
 
2710
        }
 
2711
 
 
2712
        // Send cancellation to deleted attendees
 
2713
        if ($deletedRecips && !empty($deletedRecips)) {
 
2714
            $new = $this->createOutgoingMessage();
 
2715
 
 
2716
            mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
 
2717
 
 
2718
            $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
 
2719
            $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
 
2720
            $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
 
2721
            $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;    // HIGH Importance
 
2722
            if (isset($newmessageprops[PR_SUBJECT])) {
 
2723
                $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
 
2724
            }
 
2725
 
 
2726
            mapi_setprops($new, $newmessageprops);
 
2727
            mapi_message_savechanges($new);
 
2728
 
 
2729
            // Submit message to non-resource recipients
 
2730
            mapi_message_submitmessage($new);
 
2731
        }
 
2732
 
 
2733
        // Set properties on meeting object in calendar
 
2734
        // Set requestsent to 'true' (turns on 'tracking', etc)
 
2735
        $props = array();
 
2736
        $props[$this->proptags['meetingstatus']] = olMeeting;
 
2737
        $props[$this->proptags['responsestatus']] = olResponseOrganized;
 
2738
        $props[$this->proptags['requestsent']] = (!empty($recipients)) || ($this->includesResources && !$this->errorSetResource);
 
2739
        $props[$this->proptags['attendee_critical_change']] = time();
 
2740
        $props[$this->proptags['owner_critical_change']] = time();
 
2741
        $props[$this->proptags['meetingtype']] = mtgRequest;
 
2742
        // save the new updatecounter to exception/recurring series/normal meeting
 
2743
        $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
 
2744
 
 
2745
        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
 
2746
        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
 
2747
        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
 
2748
 
 
2749
        mapi_setprops($message, $props);
 
2750
 
 
2751
        // saving of these properties on calendar item should be handled by caller function
 
2752
        // based on sending meeting request was successfull or not
 
2753
    }
 
2754
 
 
2755
    /**
 
2756
     * OL2007 uses these 4 properties to specify occurence that should be updated.
 
2757
     * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
 
2758
     * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
 
2759
     * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
 
2760
     * also additionally we are sending these properties.
 
2761
     * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID
 
2762
     * @param Object $recurObject instance of recurrence class for this message
 
2763
     * @param Array $messageprops properties of meeting object that is going to be send
 
2764
     * @param Array $newmessageprops properties of meeting request/response that is going to be send
 
2765
     */
 
2766
    function generateRecurDates($recurObject, $messageprops, &$newmessageprops)
 
2767
    {
 
2768
        if($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
 
2769
            $startDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
 
2770
            $endDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
 
2771
 
 
2772
            $startDate = explode(":", $startDate);
 
2773
            $endDate = explode(":", $endDate);
 
2774
 
 
2775
            // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
 
2776
            // RecurStartDate = year * 512 + month_number * 32 + day_number
 
2777
            $newmessageprops[$this->proptags["start_recur_date"]] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
 
2778
            // RecurStartTime = hour * 4096 + minutes * 64 + seconds
 
2779
            $newmessageprops[$this->proptags["start_recur_time"]] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
 
2780
 
 
2781
            $newmessageprops[$this->proptags["end_recur_date"]] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
 
2782
            $newmessageprops[$this->proptags["end_recur_time"]] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
 
2783
        }
 
2784
    }
 
2785
 
 
2786
    function createOutgoingMessage()
 
2787
    {
 
2788
        $sentprops = array();
 
2789
        $outbox = $this->openDefaultOutbox($this->openDefaultStore());
 
2790
 
 
2791
        $outgoing = mapi_folder_createmessage($outbox);
 
2792
        if(!$outgoing) return false;
 
2793
 
 
2794
        $addrinfo = $this->getOwnerAddress($this->store);
 
2795
        if($addrinfo) {
 
2796
            list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
 
2797
            $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
 
2798
            $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
 
2799
            $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
 
2800
            $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
 
2801
            $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
 
2802
        }
 
2803
 
 
2804
        $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
 
2805
 
 
2806
        mapi_setprops($outgoing, $sentprops);
 
2807
 
 
2808
        return $outgoing;
 
2809
    }
 
2810
 
 
2811
    /**
 
2812
     * Function which checks received meeting request is either old(outofdate) or new.
 
2813
     * @return boolean true if meeting request is outofdate else false if it is new
 
2814
     */
 
2815
    function isMeetingOutOfDate()
 
2816
    {
 
2817
        $result = false;
 
2818
        $store = $this->store;
 
2819
        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']));
 
2820
 
 
2821
        if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
 
2822
            return true;
 
2823
        }
 
2824
 
 
2825
        // get the basedate to check for exception
 
2826
        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
 
2827
 
 
2828
        $calendarItems = $this->getCorrespondedCalendarItems();
 
2829
 
 
2830
        foreach($calendarItems as $calendarItem) {
 
2831
            if ($calendarItem) {
 
2832
                $calendarItemProps = mapi_getprops($calendarItem, array(
 
2833
                                                        $this->proptags['owner_critical_change'],
 
2834
                                                        $this->proptags['updatecounter'],
 
2835
                                                        $this->proptags['recurring']
 
2836
                                        ));
 
2837
 
 
2838
                // If these items is recurring and basedate is found then open exception to compare it with meeting request
 
2839
                if (isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] && $basedate) {
 
2840
                    $recurr = new Recurrence($store, $calendarItem);
 
2841
 
 
2842
                    if ($recurr->isException($basedate)) {
 
2843
                        $attach = $recurr->getExceptionAttachment($basedate);
 
2844
                        $exception = mapi_attach_openobj($attach, 0);
 
2845
                        $occurrenceItemProps = mapi_getprops($exception, array(
 
2846
                                                        $this->proptags['owner_critical_change'],
 
2847
                                                        $this->proptags['updatecounter']
 
2848
                                            ));
 
2849
                    }
 
2850
 
 
2851
                    // we found the exception, compare with it
 
2852
                    if(isset($occurrenceItemProps)) {
 
2853
                        if ((isset($occurrenceItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $occurrenceItemProps[$this->proptags['updatecounter']])
 
2854
                            || (isset($occurrenceItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $occurrenceItemProps[$this->proptags['owner_critical_change']])) {
 
2855
 
 
2856
                            mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
 
2857
                            mapi_savechanges($this->message);
 
2858
                            $result = true;
 
2859
                        }
 
2860
                    } else {
 
2861
                        // we are not able to find exception, could mean that a significant change has occured on series
 
2862
                        // and it deleted all exceptions, so compare with series
 
2863
                        if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
 
2864
                            || (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
 
2865
 
 
2866
                            mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
 
2867
                            mapi_savechanges($this->message);
 
2868
                            $result = true;
 
2869
                        }
 
2870
                    }
 
2871
                } else {
 
2872
                    // normal / recurring series
 
2873
                    if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
 
2874
                            || (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
 
2875
 
 
2876
                        mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
 
2877
                        mapi_savechanges($this->message);
 
2878
                        $result = true;
 
2879
                    }
 
2880
                }
 
2881
            }
 
2882
        }
 
2883
 
 
2884
        return $result;
 
2885
    }
 
2886
 
 
2887
    /**
 
2888
     * Function which checks received meeting request is updated later or not.
 
2889
     * @return boolean true if meeting request is updated later.
 
2890
     * @TODO: Implement handling for recurrings and exceptions.
 
2891
     */
 
2892
    function isMeetingUpdated()
 
2893
    {
 
2894
        $result = false;
 
2895
        $store = $this->store;
 
2896
        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['owner_critical_change'], $this->proptags['updatecounter']));
 
2897
 
 
2898
        $calendarItems = $this->getCorrespondedCalendarItems();
 
2899
 
 
2900
        foreach($calendarItems as $calendarItem) {
 
2901
            if ($calendarItem) {
 
2902
                $calendarItemProps = mapi_getprops($calendarItem, array(
 
2903
                                                    $this->proptags['updatecounter'],
 
2904
                                                    $this->proptags['recurring']
 
2905
                                    ));
 
2906
 
 
2907
                if(isset($calendarItemProps[$this->proptags['updatecounter']]) && isset($props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
 
2908
                    $result = true;
 
2909
                }
 
2910
            }
 
2911
        }
 
2912
 
 
2913
        return $result;
 
2914
    }
 
2915
 
 
2916
    /**
 
2917
     * Checks if there has been any significant changes on appointment/meeting item.
 
2918
     * Significant changes be:
 
2919
     * 1) startdate has been changed
 
2920
     * 2) duedate has been changed OR
 
2921
     * 3) recurrence pattern has been created, modified or removed
 
2922
     *
 
2923
     * @param Array oldProps old props before an update
 
2924
     * @param Number basedate basedate
 
2925
     * @param Boolean isRecurrenceChanged for change in recurrence pattern.
 
2926
     * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
 
2927
     */
 
2928
    function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false)
 
2929
    {
 
2930
        $message = null;
 
2931
        $attach = null;
 
2932
 
 
2933
        // If basedate is specified then we need to open exception message to clear recipient responses
 
2934
        if($basedate) {
 
2935
            $recurrence = new Recurrence($this->store, $this->message);
 
2936
            if($recurrence->isException($basedate)){
 
2937
                $attach = $recurrence->getExceptionAttachment($basedate);
 
2938
                if ($attach) {
 
2939
                    $message = mapi_attach_openobj($attach, MAPI_MODIFY);
 
2940
                }
 
2941
            }
 
2942
        } else {
 
2943
            // use normal message or recurring series message
 
2944
            $message = $this->message;
 
2945
        }
 
2946
 
 
2947
        if(!$message) {
 
2948
            return;
 
2949
        }
 
2950
 
 
2951
        $newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']));
 
2952
 
 
2953
        // Check whether message is updated or not.
 
2954
        if(isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
 
2955
            return;
 
2956
        }
 
2957
 
 
2958
        if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']])
 
2959
            || ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']])
 
2960
            || $isRecurrenceChanged) {
 
2961
            $this->clearRecipientResponse($message);
 
2962
 
 
2963
            mapi_setprops($message, array($this->proptags['owner_critical_change'] => time()));
 
2964
 
 
2965
            mapi_savechanges($message);
 
2966
            if ($attach) { // Also save attachment Object.
 
2967
                mapi_savechanges($attach);
 
2968
            }
 
2969
        }
 
2970
    }
 
2971
 
 
2972
    /**
 
2973
     * Clear responses of all attendees who have replied in past.
 
2974
     * @param MAPI_MESSAGE $message on which responses should be cleared
 
2975
     */
 
2976
    function clearRecipientResponse($message)
 
2977
    {
 
2978
        $recipTable = mapi_message_getrecipienttable($message);
 
2979
        $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
 
2980
 
 
2981
        foreach($recipsRows as $recipient) {
 
2982
            if(($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer){
 
2983
                // Recipient is attendee, set the trackstatus to "Not Responded"
 
2984
                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
 
2985
            } else {
 
2986
                // Recipient is organizer, this is not possible, but for safety
 
2987
                // it is best to clear the trackstatus for him as well by setting
 
2988
                // the trackstatus to "Organized".
 
2989
                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
 
2990
            }
 
2991
            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, array($recipient));
 
2992
        }
 
2993
    }
 
2994
 
 
2995
    /**
 
2996
     * Function returns corresponded calendar items attached with
 
2997
     * the meeting request.
 
2998
     * @return Array array of correlated calendar items.
 
2999
     */
 
3000
    function getCorrespondedCalendarItems()
 
3001
    {
 
3002
        $store = $this->store;
 
3003
        $props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
 
3004
 
 
3005
        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
 
3006
 
 
3007
        // If Delegate is processing mr for Delegator then retrieve Delegator's store and calendar.
 
3008
        if (isset($props[PR_RCVD_REPRESENTING_NAME])) {
 
3009
            $delegatorStore = $this->getDelegatorStore($props);
 
3010
            $store = $delegatorStore['store'];
 
3011
            $calFolder = $delegatorStore['calFolder'];
 
3012
        } else {
 
3013
            $calFolder = $this->openDefaultCalendar();
 
3014
        }
 
3015
 
 
3016
        // Finding item in calendar with GlobalID(0x3), not necessary that attendee is having recurring item, he/she can also have only a occurrence
 
3017
        $entryids = $this->findCalendarItems($props[$this->proptags['goid']], $calFolder);
 
3018
 
 
3019
        // Basedate found, so this meeting request is an update of an occurrence.
 
3020
        if ($basedate) {
 
3021
            if (!$entryids) {
 
3022
                // Find main recurring item in calendar with GlobalID(0x23)
 
3023
                $entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
 
3024
            }
 
3025
        }
 
3026
 
 
3027
        $calendarItems = array();
 
3028
        if ($entryids) {
 
3029
            foreach($entryids as $entryid) {
 
3030
                $calendarItems[] = mapi_msgstore_openentry($store, $entryid);
 
3031
            }
 
3032
        }
 
3033
 
 
3034
        return $calendarItems;
 
3035
    }
 
3036
 
 
3037
    /**
 
3038
     * Function which checks whether received meeting request is either conflicting with other appointments or not.
 
3039
     *@return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
 
3040
     * conflict of recurring meeting and false if meeting is not conflicting.
 
3041
     */
 
3042
    function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false)
 
3043
    {
 
3044
        $returnValue = false;
 
3045
        $conflicting = false;
 
3046
        $noOfInstances = 0;
 
3047
 
 
3048
        if (!$message) $message = $this->message;
 
3049
 
 
3050
        if (!$userStore) $userStore = $this->store;
 
3051
 
 
3052
        if (!$calFolder) {
 
3053
            $root = mapi_msgstore_openentry($userStore);
 
3054
            $rootprops = mapi_getprops($root, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
 
3055
 
 
3056
            if(!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
 
3057
                return;
 
3058
            }
 
3059
 
 
3060
            $calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
 
3061
        }
 
3062
 
 
3063
        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']));
 
3064
 
 
3065
        if ($calFolder) {
 
3066
            // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
 
3067
            if (isset($msgprops[$this->proptags['recurring']]) && $msgprops[$this->proptags['recurring']]) {
 
3068
                // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
 
3069
                $recurr = new Recurrence($userStore, $message);
 
3070
                $items = $recurr->getItems($msgprops[$this->proptags['clipstart']], $msgprops[$this->proptags['clipend']] * (24*24*60), 30);
 
3071
 
 
3072
                foreach ($items as $item) {
 
3073
                    // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
 
3074
                    $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
 
3075
 
 
3076
                    foreach ($calendarItems as $calendarItem) {
 
3077
                        if ($calendarItem[$this->proptags['busystatus']] != fbFree) {
 
3078
                            /**
 
3079
                             * Only meeting requests have globalID, normal appointments do not have globalID
 
3080
                             * so if any normal appointment if found then it is assumed to be conflict.
 
3081
                             */
 
3082
                            if(isset($calendarItem[$this->proptags['goid']])) {
 
3083
                                if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
 
3084
                                    $noOfInstances++;
 
3085
                                    break;
 
3086
                                }
 
3087
                            } else {
 
3088
                                $noOfInstances++;
 
3089
                                break;
 
3090
                            }
 
3091
                        }
 
3092
                    }
 
3093
                }
 
3094
 
 
3095
                $returnValue = $noOfInstances;
 
3096
            } else {
 
3097
                // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
 
3098
                $items = getCalendarItems($userStore, $calFolder, $msgprops[$this->proptags['startdate']], $msgprops[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
 
3099
 
 
3100
                foreach($items as $item) {
 
3101
                    if ($item[$this->proptags['busystatus']] != fbFree) {
 
3102
                        if(isset($item[$this->proptags['goid']])) {
 
3103
                            if (($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']])
 
3104
                                && ($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid2']])) {
 
3105
                                $conflicting = true;
 
3106
                                break;
 
3107
                            }
 
3108
                        } else {
 
3109
                            $conflicting = true;
 
3110
                            break;
 
3111
                        }
 
3112
                    }
 
3113
                }
 
3114
 
 
3115
                if ($conflicting) $returnValue = true;
 
3116
            }
 
3117
        }
 
3118
        return $returnValue;
 
3119
    }
 
3120
 
 
3121
    /**
 
3122
     *  Function which adds organizer to recipient list which is passed.
 
3123
     *  This function also checks if it has organizer.
 
3124
     *
 
3125
     * @param array $messageProps message properties
 
3126
     * @param array $recipients    recipients list of message.
 
3127
     * @param boolean $isException true if we are processing recipient of exception
 
3128
     */
 
3129
    function addDelegator($messageProps, &$recipients)
 
3130
    {
 
3131
        $hasDelegator = false;
 
3132
        // Check if meeting already has an organizer.
 
3133
        foreach ($recipients as $key => $recipient){
 
3134
            if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS])
 
3135
                $hasDelegator = true;
 
3136
        }
 
3137
 
 
3138
        if (!$hasDelegator){
 
3139
            // Create delegator.
 
3140
            $delegator = array();
 
3141
            $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
 
3142
            $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
 
3143
            $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
 
3144
            $delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
 
3145
            $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
 
3146
            $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
 
3147
            $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
 
3148
            $delegator[PR_RECIPIENT_FLAGS] = recipSendable;
 
3149
 
 
3150
            // Add organizer to recipients list.
 
3151
            array_unshift($recipients, $delegator);
 
3152
        }
 
3153
    }
 
3154
 
 
3155
    function getDelegatorStore($messageprops)
 
3156
    {
 
3157
        // Find the organiser of appointment in addressbook
 
3158
        $delegatorName = array(array(PR_DISPLAY_NAME => $messageprops[PR_RCVD_REPRESENTING_NAME]));
 
3159
        $ab = mapi_openaddressbook($this->session);
 
3160
        $user = mapi_ab_resolvename($ab, $delegatorName, EMS_AB_ADDRESS_LOOKUP);
 
3161
 
 
3162
        // Get StoreEntryID by username
 
3163
        $delegatorEntryid = mapi_msgstore_createentryid($this->store, $user[0][PR_EMAIL_ADDRESS]);
 
3164
        // Open store of the delegator
 
3165
        $delegatorStore = mapi_openmsgstore($this->session, $delegatorEntryid);
 
3166
        // Open root folder
 
3167
        $delegatorRoot = mapi_msgstore_openentry($delegatorStore, null);
 
3168
        // Get calendar entryID
 
3169
        $delegatorRootProps = mapi_getprops($delegatorRoot, array(PR_IPM_APPOINTMENT_ENTRYID));
 
3170
        // Open the calendar Folder
 
3171
        $calFolder = mapi_msgstore_openentry($delegatorStore, $delegatorRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
 
3172
 
 
3173
        return Array('store' => $delegatorStore, 'calFolder' => $calFolder);
 
3174
    }
 
3175
 
 
3176
    /**
 
3177
     * Function returns extra info about meeting timing along with message body
 
3178
     * which will be included in body while sending meeting request/response.
 
3179
     *
 
3180
     * @return string $meetingTimeInfo info about meeting timing along with message body
 
3181
     */
 
3182
    function getMeetingTimeInfo()
 
3183
    {
 
3184
        return $this->meetingTimeInfo;
 
3185
    }
 
3186
 
 
3187
    /**
 
3188
     * Function sets extra info about meeting timing along with message body
 
3189
     * which will be included in body while sending meeting request/response.
 
3190
     *
 
3191
     * @param string $meetingTimeInfo info about meeting timing along with message body
 
3192
     */
 
3193
    function setMeetingTimeInfo($meetingTimeInfo)
 
3194
    {
 
3195
        $this->meetingTimeInfo = $meetingTimeInfo;
 
3196
    }
 
3197
}
 
3198
?>
 
 
b'\\ No newline at end of file'