3
* Copyright 2005 - 2013 Zarafa B.V.
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:
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:
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
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).
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.
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.
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/>.
51
class Meetingrequest {
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())
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.
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
80
* - Call sendMeetingRequest(), this sends a copy of the item with some extra properties
82
* Updating a meeting request:
83
* - Create Meetingrequest class instance
84
* - Call updateMeetingRequest(), this updates the counters
85
* - Call sendMeetingRequest()
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
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);
115
* Indication whether the setting of resources in a Meeting Request is success (false) or if it
116
* has failed (integer).
118
var $errorSetResource;
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.
127
* The $session variable is optional, but required if the following features
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
134
function Meetingrequest($store, $message, $session = false, $enableDirectBooking = true)
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;
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);
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
202
function setDirectBooking($directBookingSetting)
204
$this->enableDirectBooking = $directBookingSetting;
208
* Returns TRUE if the message pointed to is an incoming meeting request and should
209
* therefore be replied to with doAccept or doDecline()
211
function isMeetingRequest()
213
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
215
if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request")
220
* Returns TRUE if the message pointed to is a returning meeting request response
222
function isMeetingRequestResponse()
224
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
226
if(isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp") === 0)
231
* Returns TRUE if the message pointed to is a cancellation request
233
function isMeetingCancellation()
235
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
237
if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Canceled")
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
248
function processMeetingRequestResponseAsDelegate()
250
if(!$this->isMeetingRequestResponse())
253
$messageprops = mapi_getprops($this->message);
255
$goid2 = $messageprops[$this->proptags['goid2']];
257
if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
260
// Find basedate in GlobalID(0x3), this can be a response for an occurrence
261
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
263
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
264
$delegatorStore = $this->getDelegatorStore($messageprops);
265
$userStore = $delegatorStore['store'];
266
$calFolder = $delegatorStore['calFolder'];
269
$calendaritems = $this->findCalendarItems($goid2, $calFolder);
271
// $calendaritems now contains the ENTRYID's of all the calendar items to which
272
// this meeting request points.
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);
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
292
function processMeetingRequestResponse()
294
if(!$this->isLocalOrganiser())
297
if(!$this->isMeetingRequestResponse())
300
// Get information we need from the response message
301
$messageprops = mapi_getprops($this->message, Array(
302
$this->proptags['goid'],
303
$this->proptags['goid2'],
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,
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']));
318
$goid2 = $messageprops[$this->proptags['goid2']];
320
if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
323
// Find basedate in GlobalID(0x3), this can be a response for an occurrence
324
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
326
$calendaritems = $this->findCalendarItems($goid2);
328
// $calendaritems now contains the ENTRYID's of all the calendar items to which
329
// this meeting request points.
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);
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
349
function processResponse($store, $entryid, $basedate, $messageprops)
352
$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
353
$messageclass = $messageprops[PR_MESSAGE_CLASS];
354
$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
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']));
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;
368
* Check if meeting is updated or not in organizer's calendar
370
$data["meeting_updated"] = $this->isMeetingUpdated();
372
if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
373
// meeting is already processed
376
mapi_setprops($this->message, Array(PR_PROCESSED => true));
377
mapi_savechanges($this->message);
380
// if meeting is updated in organizer's calendar then we don't need to process
382
if($data['meeting_updated'] === true) {
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);
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']
398
// Create/modify exception
399
if($recurr->isException($basedate)) {
400
$recurr->modifyException($exception_props, $basedate);
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);
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);
410
$recurr->createException($exception_props, $basedate, false, $recips);
413
mapi_message_savechanges($calendaritem);
415
$attach = $recurr->getExceptionAttachment($basedate);
417
$recurringItem = $calendaritem;
418
$calendaritem = mapi_attach_openobj($attach, MAPI_MODIFY);
424
// Get the recipients of the calendar item
425
$reciptable = mapi_message_getrecipienttable($calendaritem);
426
$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
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
435
foreach($recipients as $recipient) {
437
if(isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID],$senderentryid)) {
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.
444
if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
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']];
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']];
459
mapi_message_modifyrecipients($calendaritem, MODRECIP_MODIFY, Array($recipient));
461
if(isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
465
// If the recipient was not found in the original calendar item,
466
// then add the recpient as a new optional recipient
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;
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']];
484
mapi_message_modifyrecipients($calendaritem, MODRECIP_ADD, Array($recipient));
486
if($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
490
//TODO: Upate counter proposal number property on message
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.
494
// If this is a counter proposal, set the counter proposal indicator boolean
495
if(isset($messageprops[$this->proptags['counter_proposal']])){
497
if($messageprops[$this->proptags['counter_proposal']]){
498
$props[$this->proptags['counter_proposal']] = true;
500
$props[$this->proptags['counter_proposal']] = false;
503
mapi_message_setprops($calendaritem, $props);
506
mapi_message_savechanges($calendaritem);
507
if (isset($attach)) {
508
mapi_message_savechanges($attach);
509
mapi_message_savechanges($recurringItem);
517
* Process an incoming meeting request cancellation. This updates the
518
* appointment in your calendar to show that the meeting has been cancelled.
520
function processMeetingCancellation()
522
if($this->isLocalOrganiser())
525
if(!$this->isMeetingCancellation())
528
if(!$this->isInCalendar())
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;
542
$goid = $messageprops[$this->proptags['goid']]; //GlobalID (0x3)
546
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
547
$delegatorStore = $this->getDelegatorStore($messageprops);
548
$store = $delegatorStore['store'];
549
$calFolder = $delegatorStore['calFolder'];
551
$calFolder = $this->openDefaultCalendar();
554
// First, find the items in the calendar by GOID
555
$calendaritems = $this->findCalendarItems($goid, $calFolder);
556
$basedate = $this->getBasedateFromGlobalID($goid);
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']];
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);
571
$calendaritemProps = mapi_getprops($calendaritem, array($this->proptags['recurring']));
572
if ($calendaritemProps[$this->proptags['recurring']]){
573
$recurr = new Recurrence($store, $calendaritem);
576
$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
578
if($recurr->isException($basedate))
579
$recurr->modifyException($messageprops, $basedate);
581
$recurr->createException($messageprops, $basedate);
583
mapi_savechanges($calendaritem);
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);
600
* Returns true if the item is already in the calendar
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']];
608
$basedate = $this->getBasedateFromGlobalID($goid);
610
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
611
$delegatorStore = $this->getDelegatorStore($messageprops);
612
$calFolder = $delegatorStore['calFolder'];
614
$calFolder = $this->openDefaultCalendar();
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
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);
633
return is_array($entryid);
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
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
652
function doAccept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store=false, $basedate = false)
654
if($this->isLocalOrganiser())
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));
661
* if this function is called automatically with meeting request object then there will be
663
* 1) meeting request is opened first time, in this case make a tentative appointment in
665
* 2) after this every subsequest request to open meeting request will not do any processing
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
672
mapi_setprops($this->message, Array(PR_PROCESSED => true));
673
mapi_message_savechanges($this->message);
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);
681
$store = $delegatorStore['store'];
682
$calFolder = $delegatorStore['calFolder'];
684
$calFolder = $this->openDefaultCalendar();
685
$store = $this->store;
688
return $this->accept($tentative, $sendresponse, $move, $newProposedStartTime, $newProposedEndTime, $body, $userAction, $store, $calFolder, $basedate);
691
function accept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store, $calFolder, $basedate = false)
693
$messageprops = mapi_getprops($this->message);
696
if (isset($messageprops[PR_DELEGATED_BY_RULE]))
699
$goid = $messageprops[$this->proptags['goid2']];
701
// Retrieve basedate from globalID, if it is not recieved as argument
703
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
706
$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $newProposedStartTime, $newProposedEndTime, $body, $store, $basedate, $calFolder);
708
$entryids = $this->findCalendarItems($goid, $calFolder);
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']));
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.
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
722
if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] < $prevcounterprops[$this->proptags['updatecounter']]) {
724
} else if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] == $prevcounterprops[$this->proptags['updatecounter']]) {
725
if($userAction == false && !$basedate) {
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;
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.
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.
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);
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;
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);
766
// Recurring item not found, so create new meeting in Calendar
768
$calendarItem = mapi_folder_createmessage($calFolder);
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;
777
if (isset($props[$this->proptags['intendedbusystatus']])) {
778
if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
779
$props[$this->proptags['busystatus']] = $tentative;
781
$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
783
// we already have intendedbusystatus value in $props so no need to copy it
785
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
789
// if user has responded then set replytime
790
$props[$this->proptags['replytime']] = time();
793
mapi_setprops($calendarItem, $props);
795
// Copy attachments too
796
$this->replaceAttachments($this->message, $calendarItem);
797
// Copy recipients too
798
$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
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) {
806
$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
808
// Save occurrence into main recurring item as exception
809
if ($occurrenceItem) {
810
$occurrenceItemProps = mapi_getprops($occurrenceItem, array($this->proptags['goid'], $this->proptags['recurring']));
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);
819
mapi_savechanges($calendarItem);
821
$wastebasket = $this->openDefaultWastebasket();
822
mapi_folder_copymessages($calFolder, Array($props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
824
$entryid = $props[PR_ENTRYID];
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.
831
$calendarItem = false;
833
// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
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);
843
// Main recurring item is found, so now update exception
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];
851
if (!$calendarItem) {
852
$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
854
if (is_array($items))
855
mapi_folder_deletemessages($calFolder, $items);
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();
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 */
871
$calItemProps = Array();
872
$calItemProps[PR_MESSAGE_CLASS] = "IPM.Appointment";
874
if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
875
if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
876
$calItemProps[$this->proptags['busystatus']] = $tentative;
878
$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
880
$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
882
$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
885
// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
886
$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
888
// if user has responded then set replytime
889
$calItemProps[$this->proptags['replytime']] = time();
892
mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
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));
897
// add owner to recipient table
899
$this->addOrganizer($props, $recips);
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.
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);
912
mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
915
mapi_message_savechanges($calmsg);
917
// Move the message to the wastebasket
918
$wastebasket = $this->openDefaultWastebasket();
919
mapi_folder_copymessages($sourcefolder, array($old_entryid[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
921
$messageprops = mapi_getprops($calmsg, array(PR_ENTRYID));
922
$entryid = $messageprops[PR_ENTRYID];
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);
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;
932
if (isset($props[$this->proptags['intendedbusystatus']])) {
933
if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
934
$props[$this->proptags['busystatus']] = $tentative;
936
$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
938
// we already have intendedbusystatus value in $props so no need to copy it
940
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
943
// ZP-341 - we need to copy as well the attachments
944
// Copy attachments too
945
$this->replaceAttachments($this->message, $new);
949
// if user has responded then set replytime
950
$props[$this->proptags['replytime']] = time();
953
mapi_setprops($new, $proposeNewTimeProps + $props);
955
$reciptable = mapi_message_getrecipienttable($this->message);
959
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
961
$this->addOrganizer($props, $recips);
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.
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);
974
mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
976
mapi_message_savechanges($new);
978
$props = mapi_getprops($new, array(PR_ENTRYID));
979
$entryid = $props[PR_ENTRYID];
984
// Here only properties are set on calendaritem, because user is responding from calendar.
986
$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
988
if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
989
if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
990
$props[$this->proptags['busystatus']] = $tentative;
992
$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
994
$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
996
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
999
$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1000
$props[$this->proptags['replytime']] = time();
1003
$recurr = new Recurrence($store, $this->message);
1005
// Copy recipients list
1006
$reciptable = mapi_message_getrecipienttable($this->message);
1007
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1009
if($recurr->isException($basedate)) {
1010
$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1012
$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1013
$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
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];
1020
$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1023
mapi_setprops($this->message, $proposeNewTimeProps + $props);
1025
mapi_savechanges($this->message);
1027
$entryid = $messageprops[PR_ENTRYID];
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.
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
1045
function doDecline($sendresponse, $store=false, $basedate = false, $body = false)
1048
$calendaritem = false;
1049
if($this->isLocalOrganiser())
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));
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);
1059
$store = $delegatorStore['store'];
1060
$calFolder = $delegatorStore['calFolder'];
1062
$calFolder = $this->openDefaultCalendar();
1063
$store = $this->store;
1066
$goid = $messageprops[$this->proptags['goid']];
1068
// First, find the items in the calendar by GlobalObjid (0x3)
1069
$entryids = $this->findCalendarItems($goid, $calFolder);
1072
$basedate = $this->getBasedateFromGlobalID($goid);
1075
$this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
1078
// use CleanGlobalObjid (0x23)
1079
$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
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);
1085
// Recurring item is found, now delete exception
1087
$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1090
if ($this->isMeetingRequest())
1091
$calendaritem = false;
1096
if (!$calendaritem) {
1097
$calendar = $this->openDefaultCalendar();
1099
if(!empty($entryids)) {
1100
mapi_folder_deletemessages($calendar, $entryids);
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();
1107
$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
1109
// Release the message
1110
$this->message = null;
1112
// Move the message to the waste basket
1113
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
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
1123
function doRemoveFromCalendar($basedate)
1125
if($this->isLocalOrganiser())
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']];
1132
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1133
$delegatorStore = $this->getDelegatorStore($messageprops);
1134
$store = $delegatorStore['store'];
1135
$calFolder = $delegatorStore['calFolder'];
1137
$calFolder = $this->openDefaultCalendar();
1140
$wastebasket = $this->openDefaultWastebasket();
1141
$sourcefolder = $this->openParentFolder();
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) {
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.
1151
// Retrieve GlobalID and find basedate in it.
1152
$basedate = $this->getBasedateFromGlobalID($goid);
1154
// Basedate found, Now find item.
1156
$guid = $this->setBasedateInGlobalID($goid);
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);
1166
$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1173
// It is normal/recurring meeting item.
1175
if (!isset($calFolder)) $calFolder = $this->openDefaultCalendar();
1177
$entryids = $this->findCalendarItems($goid, $calFolder);
1179
if(is_array($entryids)){
1180
// Move the calendaritem to the waste basket
1181
mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
1185
// Release the message
1186
$this->message = null;
1188
// Move the message to the waste basket
1189
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
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);
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.
1209
if($this->isLocalOrganiser())
1211
if(!$this->isMeetingCancellation())
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']];
1218
$entryids = $this->findCalendarItems($goid);
1219
$calendar = $this->openDefaultCalendar();
1221
mapi_folder_deletemessages($calendar, $entryids);
1223
// All we have to do to decline, is to move the item to the waste basket
1225
$wastebasket = $this->openDefaultWastebasket();
1226
$sourcefolder = $this->openParentFolder();
1228
$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
1230
// Release the message
1231
$this->message = null;
1233
// Move the message to the waste basket
1234
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
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.
1244
function setMeetingRequest($basedate = false)
1246
$props = mapi_getprops($this->message, Array($this->proptags['updatecounter']));
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));
1253
// Create a new appointment id for this item
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;
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;
1266
mapi_setprops($this->message, $props);
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
1277
function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false)
1279
$this->includesResources = false;
1280
$this->nonAcceptingResources = Array();
1282
// Get the properties of the message
1283
$messageprops = mapi_getprops($this->message, Array($this->proptags['recurring']));
1285
/*****************************************************************************************
1286
* Submit message to non-resource recipients
1288
// Set BusyStatus to olTentative (1)
1289
// Set MeetingStatus to olMeetingReceived
1290
// Set ResponseStatus to olResponseNotResponded
1293
* While sending recurrence meeting exceptions are not send as attachments
1294
* because first all exceptions are send and then recurrence meeting is sent.
1296
if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1298
$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1300
if (!$this->errorSetResource) {
1301
$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1303
// First send meetingrequest for recurring item
1304
$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
1306
// Then send all meeting request for all exceptions
1307
$exceptions = $recurr->getAllExceptions();
1309
foreach($exceptions as $exceptionBasedate) {
1310
$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1313
$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1314
$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
1315
mapi_savechanges($attach);
1321
// Basedate found, an exception is to be send
1323
$recurr = new Recurrence($this->openDefaultStore(), $this->message);
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);
1329
$attach = $recurr->getExceptionAttachment($basedate);
1332
$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1334
// Book resource for this occurrence
1335
$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1337
if (!$this->errorSetResource) {
1338
// Save all previous changes
1339
mapi_savechanges($this->message);
1341
$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
1342
mapi_savechanges($occurrenceItem);
1343
mapi_savechanges($attach);
1348
// This is normal meeting
1349
$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1351
if (!$this->errorSetResource) {
1352
$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
1357
if(isset($this->errorSetResource) && $this->errorSetResource){
1359
'error' => $this->errorSetResource,
1360
'displayname' => $this->recipientDisplayname
1368
function getFreeBusyInfo($entryID,$start,$end)
1371
$fbsupport = mapi_freebusysupport_open($this->session);
1373
if(mapi_last_hresult() != NOERROR) {
1374
if(function_exists("dump")) {
1375
dump("Error in opening freebusysupport object.");
1380
$fbDataArray = mapi_freebusysupport_loaddata($fbsupport, array($entryID));
1382
if($fbDataArray[0] != NULL){
1383
foreach($fbDataArray as $fbDataUser){
1384
$rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1385
if($rangeuser1 == NULL){
1389
$enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1390
mapi_freebusyenumblock_reset($enumblock);
1393
$blocks = mapi_freebusyenumblock_next($enumblock, 100);
1397
foreach($blocks as $blockItem){
1398
$result[] = $blockItem;
1404
mapi_freebusysupport_close($fbsupport);
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.
1415
function updateMeetingRequest($basedate = false)
1417
$messageprops = mapi_getprops($this->message, Array($this->proptags['last_updatecounter'], $this->proptags['goid']));
1419
if(!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
1420
$this->setMeetingRequest($basedate);
1422
$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
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));
1432
* Returns TRUE if we are the organiser of the meeting.
1434
function isLocalOrganiser()
1436
if($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
1437
$messageid = $this->getAppointmentEntryID();
1439
if(!isset($messageid))
1442
$message = mapi_msgstore_openentry($this->store, $messageid);
1444
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
1445
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
1447
$recurr = new Recurrence($this->store, $message);
1448
$attach = $recurr->getExceptionAttachment($basedate);
1450
$occurItem = mapi_attach_openobj($attach);
1451
$occurItemProps = mapi_getprops($occurItem, Array($this->proptags['responsestatus']));
1455
$messageprops = mapi_getprops($message, Array($this->proptags['responsestatus']));
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.
1463
if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized)
1464
|| (isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized))
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.
1474
function getAppointmentEntryID()
1476
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid2']));
1478
$goid2 = $messageprops[$this->proptags['goid2']];
1480
$items = $this->findCalendarItems($goid2);
1485
// There should be just one item. If there are more, we just take the first one
1489
/***************************************************************************************************
1490
* Support functions - INTERNAL ONLY
1491
***************************************************************************************************
1495
* Return the tracking status of a recipient based on the IPM class (passed)
1497
function getTrackStatus($class) {
1498
$status = olRecipientTrackStatusNone;
1501
case "IPM.Schedule.Meeting.Resp.Pos":
1502
$status = olRecipientTrackStatusAccepted;
1505
case "IPM.Schedule.Meeting.Resp.Tent":
1506
$status = olRecipientTrackStatusTentative;
1509
case "IPM.Schedule.Meeting.Resp.Neg":
1510
$status = olRecipientTrackStatusDeclined;
1516
function openParentFolder() {
1517
$messageprops = mapi_getprops($this->message, Array(PR_PARENT_ENTRYID));
1519
$parentfolder = mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1520
return $parentfolder;
1523
function openDefaultCalendar() {
1524
return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
1527
function openDefaultOutbox($store=false) {
1528
return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1531
function openDefaultWastebasket() {
1532
return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
1535
function getDefaultWastebasketEntryID() {
1536
return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
1539
function getDefaultSentmailEntryID($store=false) {
1540
return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1543
function getDefaultFolderEntryID($prop) {
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
1555
$inboxprops = mapi_getprops($inbox, Array($prop));
1556
if(!isset($inboxprops[$prop]))
1559
return $inboxprops[$prop];
1562
function openDefaultFolder($prop) {
1563
$entryid = $this->getDefaultFolderEntryID($prop);
1564
$folder = mapi_msgstore_openentry($this->store, $entryid);
1569
function getBaseEntryID($prop, $store=false) {
1570
$storeprops = mapi_getprops( (($store)?$store:$this->store) , Array($prop));
1571
if(!isset($storeprops[$prop]))
1574
return $storeprops[$prop];
1577
function openBaseFolder($prop, $store=false) {
1578
$entryid = $this->getBaseEntryID($prop, $store);
1579
$folder = mapi_msgstore_openentry( (($store)?$store:$this->store) , $entryid);
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
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'],
1609
$this->proptags['is_exception']
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);
1619
// Exception found, Now retrieve properties
1620
$imessage = mapi_attach_openobj($exception, 0);
1621
$imsgprops = mapi_getprops($imessage);
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']];
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']];
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];
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);
1642
$props[$this->proptags['meetingstatus']] = olNonMeeting;
1643
$props[$this->proptags['responsestatus']] = olResponseNone;
1646
$props[$this->proptags['recurring']] = false;
1647
$props[$this->proptags['is_exception']] = true;
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) {
1655
$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
1658
$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
1660
$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1662
$calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
1663
$recurr = new Recurrence($store, $calendaritem);
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']];
1673
if(!empty($messageprops[$this->proptags['recurrence_data']])) {
1674
$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
1677
$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
1678
$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
1680
$this->generateRecurDates($recurr, $messageprops, $props);
1683
// Create a response message
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;
1692
case olResponseAccepted:
1693
$classpostfix = "Pos";
1694
$subjectprefix = _("Accepted");
1696
case olResponseDeclined:
1697
$classpostfix = "Neg";
1698
$subjectprefix = _("Declined");
1700
case olResponseTentative:
1701
$classpostfix = "Tent";
1702
$subjectprefix = _("Tentatively accepted");
1706
if($proposalStartTime && $proposalEndTime){
1707
// if attendee has proposed new time then change subject prefix
1708
$subjectprefix = _("New Time Proposed");
1711
$props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
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];
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']];
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);
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;
1733
//Set body message in Appointment
1735
$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
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']];
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']];
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']];
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));
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));
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";
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));
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];
1785
// pass the default store to get the required store.
1786
$outbox = $this->openDefaultOutbox($defaultStore);
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);
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)
1801
function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
1803
// Open the Calendar
1804
$calendar = $this->openDefaultCalendar();
1807
// Find the item by restricting all items to the correct ID
1808
$restrict = Array(RES_AND, Array());
1810
array_push($restrict[1], Array(RES_PROPERTY,
1811
Array(RELOP => RELOP_EQ,
1812
ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
1817
$calendarcontents = mapi_folder_getcontentstable($calendar);
1819
$rows = mapi_table_queryallrows($calendarcontents, Array(PR_ENTRYID), $restrict);
1824
$calendaritems = Array();
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];
1831
return $calendaritems;
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.
1839
return $entryid1 == $entryid2;
1841
$smtp1 = $this->getSMTPAddress($entryid1);
1842
$smtp2 = $this->getSMTPAddress($entryid2);
1844
if($smtp1 == $smtp2)
1850
// Gets the SMTP address of the passed addressbook entryid
1851
function getSMTPAddress($entryid) {
1855
$ab = mapi_openaddressbook($this->session);
1857
$abitem = mapi_ab_openentry($ab, $entryid);
1862
$props = mapi_getprops($abitem, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
1864
if($props[PR_ADDRTYPE] == "SMTP") {
1865
return $props[PR_EMAIL_ADDRESS];
1867
else return $props[PR_SMTP_ADDRESS];
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
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.
1880
function getOwnerAddress($store, $fallbackToLoggedInUser = true)
1885
$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID));
1887
$ownerEntryId = false;
1888
if(isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
1889
$ownerEntryId = $storeProps[PR_USER_ENTRYID];
1892
if(isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
1893
$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1897
$ab = mapi_openaddressbook($this->session);
1899
$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
1903
$ownerProps = mapi_getprops($zarafaUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
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;
1911
return array($name, $emailAddr, $addrType, $entryId, $searchKey);
1917
// Opens this session's default message store
1918
function openDefaultStore()
1920
$storestable = mapi_getmsgstorestable($this->session);
1921
$rows = mapi_table_queryallrows($storestable, array(PR_ENTRYID, PR_DEFAULT_STORE));
1924
foreach($rows as $row) {
1925
if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
1926
$entryid = $row[PR_ENTRYID];
1934
return mapi_openmsgstore($this->session, $entryid);
1937
* Function which adds organizer to recipient list which is passed.
1938
* This function also checks if it has organizer.
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
1944
function addOrganizer($messageProps, &$recipients, $isException = false){
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;
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;
1969
// Add organizer to recipients list.
1970
array_unshift($recipients, $organizer);
1975
* Function adds recipients in recips array from the string.
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.
1981
function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO)
1983
$extraRecipient = array();
1984
$recipArray = explode(";", $recipString);
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);
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
2004
function doRemoveExceptionFromCalendar($basedate, $message, $store)
2006
$recurr = new Recurrence($store, $message);
2007
$recurr->createException(array(), $basedate, true);
2008
mapi_savechanges($message);
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
2016
function getBasedateFromGlobalID($goid)
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));
2024
if ($day && $month && $year)
2025
return gmmktime(0, 0, 0, $month, $day, $year);
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
2036
function setBasedateInGlobalID($goid, $basedate = false)
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';
2043
return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
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
2051
function replaceAttachments($copy_from, $copy_to, $copyExceptions = true)
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));
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]))
2062
mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
2065
$attachmentTable = false;
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));
2072
foreach($attachments as $attach_props){
2073
if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
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);
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.
2088
function replaceRecipients($copy_from, $copy_to, $isDelegate = false)
2090
$recipienttable = mapi_message_getrecipienttable($copy_from);
2092
// If delegate, then do not add the delegate in recipients
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);
2098
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
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));
2107
mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
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
2115
function bookResources($message, $cancel, $prefix, $basedate = false)
2117
if(!$this->enableDirectBooking)
2120
// Get the properties of the message
2121
$messageprops = mapi_getprops($message);
2124
$recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID));
2126
$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2127
$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
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]);
2137
if (isset($messageprops[$this->proptags['recurring']])) $messageprops[$this->proptags['recurring']] = false;
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();
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)
2157
$recipienttable = mapi_message_getrecipienttable($message);
2158
$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2160
$this->errorSetResource = false;
2161
$resourceRecipData = Array();
2163
// Put appointment into store resource users
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];
2174
$resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
2175
$resourceABEntryID = $ret[0][PR_ENTRYID];
2177
// Get StoreEntryID by username
2178
$user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
2180
// Open store of the user
2181
$userStore = mapi_openmsgstore($this->session, $user_entryid);
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));
2187
// Open Calendar folder [check hresult==0]
2188
$accessToFolder = false;
2190
$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2192
$calFolderProps = mapi_getProps($calFolder, Array(PR_ACCESS));
2193
if(($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0){
2194
$accessToFolder = true;
2197
} catch (MAPIException $e) {
2199
$this->errorSetResource = 1; // No access
2202
if($accessToFolder) {
2204
* Get the LocalFreebusy message that contains the properties that
2205
* are set to accept or decline resource meeting requests
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));
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){
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.
2221
//$errorSetResource = 2;
2222
$this->nonAcceptingResources[] = $resourceRecipients[$i];
2224
if($declineRecurringMeetingRequests && !$cancel){
2225
// Check if appointment is recurring
2226
if($messageprops[ $this->proptags['recurring'] ]){
2227
$this->errorSetResource = 3;
2230
if($declineConflictingMeetingRequests && !$cancel){
2231
// Check for conflicting items
2232
$conflicting = false;
2234
// Open the calendar
2235
$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2238
if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops))
2239
$conflicting = true;
2241
$this->errorSetResource = 1; // No access
2245
$this->errorSetResource = 4; // Conflict
2252
if(!$this->errorSetResource && $accessToFolder){
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.
2258
$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2261
* If no entry is found then
2262
* 1) Resource doesnt have meeting in Calendar. Seriously!!
2264
* 2) We were looking for occurrence item but Resource has whole series
2268
* Now search on CleanGlobalID(0x23) WHY???
2269
* Because we are looking recurring item
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.
2275
$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
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']));
2284
if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2285
$newResourceMsg = $ResourceMsg;
2291
// Still no results found. I giveup, create new message.
2292
if (!$newResourceMsg)
2293
$newResourceMsg = mapi_folder_createmessage($calFolder);
2295
$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2298
// Prefix the subject if needed
2299
if($prefix && isset($messageprops[PR_SUBJECT])) {
2300
$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2303
// Set status to cancelled if needed
2304
$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2306
$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2307
$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2309
$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2311
$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource autmatically accepts the appointment
2313
$messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
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;
2320
$addrinfo = $this->getOwnerAddress($this->store);
2323
list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
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;
2331
$messageprops[$this->proptags['apptreplyname']] = $ownername;
2332
$messageprops[$this->proptags['replytime']] = time();
2335
if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2336
$recurr = new Recurrence($userStore, $newResourceMsg);
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);
2344
// Update occurrence
2345
if($recurr->isException($basedate))
2346
$recurr->modifyException($messageprops, $basedate, $recips);
2348
$recurr->createException($messageprops, $basedate, false, $recips);
2351
mapi_setprops($newResourceMsg, $messageprops);
2354
$this->replaceAttachments($message, $newResourceMsg);
2356
// Copy all recipients too
2357
$this->replaceRecipients($message, $newResourceMsg);
2359
// Now add organizer also to recipient table
2361
$this->addOrganizer($messageprops, $recips);
2362
mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2365
mapi_savechanges($newResourceMsg);
2367
$resourceRecipData[] = Array(
2368
'store' => $userStore,
2369
'folder' => $calFolder,
2370
'msg' => $newResourceMsg,
2372
$this->includesResources = true;
2375
* If no other errors occured and you have no access to the
2376
* folder of the resource, throw an error=1.
2378
if(!$this->errorSetResource){
2379
$this->errorSetResource = 1;
2382
for($j = 0, $len = count($resourceRecipData); $j < $len; $j++){
2384
$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2386
mapi_folder_deletemessages($resourceRecipData[$j]['folder'], Array($props[PR_ENTRYID]), DELETE_HARD_DELETE);
2388
$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2393
/**************************************************************
2394
* Set the BCC-recipients (resources) tackstatus to accepted.
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)
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();
2413
mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
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
2427
return $resourceRecipData;
2430
* Function which save an exception into recurring item
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
2441
function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false)
2443
$recurr = new Recurrence($store, $recurringItem);
2445
// Copy properties from meeting request
2446
$exception_props = mapi_getprops($occurrenceItem);
2448
// Copy recipients list
2449
$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2450
// If delegate, then do not add the delegate in recipients
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);
2456
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2460
// add owner to recipient table
2461
$this->addOrganizer($exception_props, $recips, true);
2463
// add delegator to meetings
2464
if ($isDelegate) $this->addDelegator($exception_props, $recips);
2466
$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2467
$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2468
// Set basedate property (ExceptionReplaceTime)
2470
if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2471
if($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2472
$exception_props[$this->proptags['busystatus']] = $tentative;
2474
$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2476
// we already have intendedbusystatus value in $exception_props so no need to copy it
2478
$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2482
// if user has responded then set replytime
2483
$exception_props[$this->proptags['replytime']] = time();
2486
if($recurr->isException($basedate))
2487
$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2489
$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2491
// Move the occurrenceItem to the waste basket
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);
2498
mapi_savechanges($recurringItem);
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
2510
function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false)
2512
$newmessageprops = $messageprops = mapi_getprops($this->message);
2513
$new = $this->createOutgoingMessage();
2515
// Copy the entire message into the new meeting request message
2517
// messageprops contains properties of whole recurring series
2518
// and newmessageprops contains properties of exception item
2519
$newmessageprops = mapi_getprops($message);
2521
// Ensure that the correct basedate is set in the new message
2522
$newmessageprops[$this->proptags['basedate']] = $basedate;
2524
// Set isRecurring to false, because this is an exception
2525
$newmessageprops[$this->proptags['recurring']] = false;
2527
// set LID_IS_EXCEPTION to true
2528
$newmessageprops[$this->proptags['is_exception']] = true;
2530
// Set to high importance
2531
if($cancel) $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
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);
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];
2544
// Get deleted recipiets from exception msg
2545
$restriction = Array(RES_AND,
2548
Array( ULTYPE => BMR_NEZ,
2549
ULPROPTAG => PR_RECIPIENT_FLAGS,
2550
ULMASK => recipExceptionalDeleted
2554
Array( ULTYPE => BMR_EQZ,
2555
ULPROPTAG => PR_RECIPIENT_FLAGS,
2556
ULMASK => recipOrganizer
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)
2572
$recipienttable = mapi_message_getrecipienttable($message);
2573
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2575
if (!$deletedRecips) {
2576
$deletedRecips = array_merge(array(), $recipients);
2578
$deletedRecips = array_merge($deletedRecips, $recipients);
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;
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']];
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']];
2595
$meetingTimeInfo = $this->getMeetingTimeInfo();
2597
if($meetingTimeInfo)
2598
$newmessageprops[PR_BODY] = $meetingTimeInfo;
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']];
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']];
2610
$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
2614
if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
2615
unset($newmessageprops[$this->proptags['counter_proposal']]);
2618
// Prefix the subject if needed
2619
if ($prefix && isset($newmessageprops[PR_SUBJECT]))
2620
$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
2622
mapi_setprops($new, $newmessageprops);
2625
$this->replaceAttachments($message, $new, $copyExceptions);
2627
// Retrieve only those recipient who should receive this meeting request.
2628
$stripResourcesRestriction = Array(RES_AND,
2631
Array( ULTYPE => BMR_EQZ,
2632
ULPROPTAG => PR_RECIPIENT_FLAGS,
2633
ULMASK => recipExceptionalDeleted
2637
Array( ULTYPE => BMR_EQZ,
2638
ULPROPTAG => PR_RECIPIENT_FLAGS,
2639
ULMASK => recipOrganizer
2645
// In direct-booking mode, resources do not receive a meeting request
2646
if($this->enableDirectBooking) {
2647
$stripResourcesRestriction[1][] =
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)
2656
$recipienttable = mapi_message_getrecipienttable($message);
2657
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2659
if ($basedate && empty($recipients)) {
2660
// Retrieve full list
2661
$recipienttable = mapi_message_getrecipienttable($this->message);
2662
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2664
// Save recipients in exceptions
2665
mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
2667
// Now retrieve only those recipient who should receive this meeting request.
2668
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2671
//@TODO: handle nonAcceptingResources
2673
* Add resource recipients that did not automatically accept the meeting request.
2674
* (note: meaning that they did not decline the meeting request)
2676
for($i=0;$i<count($this->nonAcceptingResources);$i++){
2677
$recipients[] = $this->nonAcceptingResources[$i];
2680
if(!empty($recipients)) {
2681
// Strip out the sender/"owner" recipient
2682
mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
2684
// Set some properties that are different in the sent request than
2685
// in the item in our calendar
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;
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
2701
$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
2702
$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2705
mapi_setprops($new, $newmessageprops);
2706
mapi_message_savechanges($new);
2708
// Submit message to non-resource recipients
2709
mapi_message_submitmessage($new);
2712
// Send cancellation to deleted attendees
2713
if ($deletedRecips && !empty($deletedRecips)) {
2714
$new = $this->createOutgoingMessage();
2716
mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
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];
2726
mapi_setprops($new, $newmessageprops);
2727
mapi_message_savechanges($new);
2729
// Submit message to non-resource recipients
2730
mapi_message_submitmessage($new);
2733
// Set properties on meeting object in calendar
2734
// Set requestsent to 'true' (turns on 'tracking', etc)
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']];
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']];
2749
mapi_setprops($message, $props);
2751
// saving of these properties on calendar item should be handled by caller function
2752
// based on sending meeting request was successfull or not
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
2766
function generateRecurDates($recurObject, $messageprops, &$newmessageprops)
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']]));
2772
$startDate = explode(":", $startDate);
2773
$endDate = explode(":", $endDate);
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]);
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]);
2786
function createOutgoingMessage()
2788
$sentprops = array();
2789
$outbox = $this->openDefaultOutbox($this->openDefaultStore());
2791
$outgoing = mapi_folder_createmessage($outbox);
2792
if(!$outgoing) return false;
2794
$addrinfo = $this->getOwnerAddress($this->store);
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;
2804
$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
2806
mapi_setprops($outgoing, $sentprops);
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
2815
function isMeetingOutOfDate()
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']));
2821
if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
2825
// get the basedate to check for exception
2826
$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
2828
$calendarItems = $this->getCorrespondedCalendarItems();
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']
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);
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']
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']])) {
2856
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2857
mapi_savechanges($this->message);
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']])) {
2866
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2867
mapi_savechanges($this->message);
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']])) {
2876
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
2877
mapi_savechanges($this->message);
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.
2892
function isMeetingUpdated()
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']));
2898
$calendarItems = $this->getCorrespondedCalendarItems();
2900
foreach($calendarItems as $calendarItem) {
2901
if ($calendarItem) {
2902
$calendarItemProps = mapi_getprops($calendarItem, array(
2903
$this->proptags['updatecounter'],
2904
$this->proptags['recurring']
2907
if(isset($calendarItemProps[$this->proptags['updatecounter']]) && isset($props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
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
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
2928
function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false)
2933
// If basedate is specified then we need to open exception message to clear recipient responses
2935
$recurrence = new Recurrence($this->store, $this->message);
2936
if($recurrence->isException($basedate)){
2937
$attach = $recurrence->getExceptionAttachment($basedate);
2939
$message = mapi_attach_openobj($attach, MAPI_MODIFY);
2943
// use normal message or recurring series message
2944
$message = $this->message;
2951
$newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']));
2953
// Check whether message is updated or not.
2954
if(isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
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);
2963
mapi_setprops($message, array($this->proptags['owner_critical_change'] => time()));
2965
mapi_savechanges($message);
2966
if ($attach) { // Also save attachment Object.
2967
mapi_savechanges($attach);
2973
* Clear responses of all attendees who have replied in past.
2974
* @param MAPI_MESSAGE $message on which responses should be cleared
2976
function clearRecipientResponse($message)
2978
$recipTable = mapi_message_getrecipienttable($message);
2979
$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
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;
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;
2991
mapi_message_modifyrecipients($message, MODRECIP_MODIFY, array($recipient));
2996
* Function returns corresponded calendar items attached with
2997
* the meeting request.
2998
* @return Array array of correlated calendar items.
3000
function getCorrespondedCalendarItems()
3002
$store = $this->store;
3003
$props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
3005
$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
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'];
3013
$calFolder = $this->openDefaultCalendar();
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);
3019
// Basedate found, so this meeting request is an update of an occurrence.
3022
// Find main recurring item in calendar with GlobalID(0x23)
3023
$entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
3027
$calendarItems = array();
3029
foreach($entryids as $entryid) {
3030
$calendarItems[] = mapi_msgstore_openentry($store, $entryid);
3034
return $calendarItems;
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.
3042
function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false)
3044
$returnValue = false;
3045
$conflicting = false;
3048
if (!$message) $message = $this->message;
3050
if (!$userStore) $userStore = $this->store;
3053
$root = mapi_msgstore_openentry($userStore);
3054
$rootprops = mapi_getprops($root, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
3056
if(!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
3060
$calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
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']));
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);
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));
3076
foreach ($calendarItems as $calendarItem) {
3077
if ($calendarItem[$this->proptags['busystatus']] != fbFree) {
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.
3082
if(isset($calendarItem[$this->proptags['goid']])) {
3083
if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
3095
$returnValue = $noOfInstances;
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));
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;
3109
$conflicting = true;
3115
if ($conflicting) $returnValue = true;
3118
return $returnValue;
3122
* Function which adds organizer to recipient list which is passed.
3123
* This function also checks if it has organizer.
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
3129
function addDelegator($messageProps, &$recipients)
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;
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;
3150
// Add organizer to recipients list.
3151
array_unshift($recipients, $delegator);
3155
function getDelegatorStore($messageprops)
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);
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);
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]);
3173
return Array('store' => $delegatorStore, 'calFolder' => $calFolder);
3177
* Function returns extra info about meeting timing along with message body
3178
* which will be included in body while sending meeting request/response.
3180
* @return string $meetingTimeInfo info about meeting timing along with message body
3182
function getMeetingTimeInfo()
3184
return $this->meetingTimeInfo;
3188
* Function sets extra info about meeting timing along with message body
3189
* which will be included in body while sending meeting request/response.
3191
* @param string $meetingTimeInfo info about meeting timing along with message body
3193
function setMeetingTimeInfo($meetingTimeInfo)
3195
$this->meetingTimeInfo = $meetingTimeInfo;
b'\\ No newline at end of file'