1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/z-push_ynh.git synced 2024-09-03 18:05:58 +02:00
z-push_ynh/sources/backend/zarafa/mapi/class.meetingrequest.php
2014-12-17 15:40:48 +00:00

3198 lines
No EOL
155 KiB
PHP
Raw Blame History

<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
class Meetingrequest {
/*
* NOTE
*
* This class is designed to modify and update meeting request properties
* and to search for linked appointments in the calendar. It does not
* - set standard properties like subject or location
* - commit property changes through savechanges() (except in accept() and decline())
*
* To set all the other properties, just handle the item as any other appointment
* item. You aren't even required to set those properties before or after using
* this class. If you update properties before REsending a meeting request (ie with
* a time change) you MUST first call updateMeetingRequest() so the internal counters
* can be updated. You can then submit the message any way you like.
*
*/
/*
* How to use
* ----------
*
* Sending a meeting request:
* - Create appointment item as normal, but as 'tentative'
* (this is the state of the item when the receiving user has received but
* not accepted the item)
* - Set recipients as normally in e-mails
* - Create Meetingrequest class instance
* - Call setMeetingRequest(), this turns on all the meeting request properties in the
* calendar item
* - Call sendMeetingRequest(), this sends a copy of the item with some extra properties
*
* Updating a meeting request:
* - Create Meetingrequest class instance
* - Call updateMeetingRequest(), this updates the counters
* - Call sendMeetingRequest()
*
* Clicking on a an e-mail:
* - Create Meetingrequest class instance
* - Check isMeetingRequest(), if true:
* - Check isLocalOrganiser(), if true then ignore the message
* - Check isInCalendar(), if not call doAccept(true, false, false). This adds the item in your
* calendar as tentative without sending a response
* - Show Accept, Tentative, Decline buttons
* - When the user presses Accept, Tentative or Decline, call doAccept(false, true, true),
* doAccept(true, true, true) or doDecline(true) respectively to really accept or decline and
* send the response. This will remove the request from your inbox.
* - Check isMeetingRequestResponse, if true:
* - Check isLocalOrganiser(), if not true then ignore the message
* - Call processMeetingRequestResponse()
* This will update the trackstatus of all recipients, and set the item to 'busy'
* when all the recipients have accepted.
* - Check isMeetingCancellation(), if true:
* - Check isLocalOrganiser(), if true then ignore the message
* - Check isInCalendar(), if not, then ignore
* Call processMeetingCancellation()
* - Show 'Remove item' button to user
* - When userpresses button, call doCancel(), which removes the item from your
* calendar and deletes the message
*/
// All properties for a recipient that are interesting
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);
/**
* Indication whether the setting of resources in a Meeting Request is success (false) or if it
* has failed (integer).
*/
var $errorSetResource;
/**
* Constructor
*
* Takes a store and a message. The message is an appointment item
* that should be converted into a meeting request or an incoming
* e-mail message that is a meeting request.
*
* The $session variable is optional, but required if the following features
* are to be used:
*
* - Sending meeting requests for meetings that are not in your own store
* - Sending meeting requests to resources, resource availability checking and resource freebusy updates
*/
function Meetingrequest($store, $message, $session = false, $enableDirectBooking = true)
{
$this->store = $store;
$this->message = $message;
$this->session = $session;
// This variable string saves time information for the MR.
$this->meetingTimeInfo = false;
$this->enableDirectBooking = $enableDirectBooking;
$properties["goid"] = "PT_BINARY:PSETID_Meeting:0x3";
$properties["goid2"] = "PT_BINARY:PSETID_Meeting:0x23";
$properties["type"] = "PT_STRING8:PSETID_Meeting:0x24";
$properties["meetingrecurring"] = "PT_BOOLEAN:PSETID_Meeting:0x5";
$properties["unknown2"] = "PT_BOOLEAN:PSETID_Meeting:0xa";
$properties["attendee_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1";
$properties["owner_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1a";
$properties["meetingstatus"] = "PT_LONG:PSETID_Appointment:0x8217";
$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
$properties["unknown6"] = "PT_LONG:PSETID_Meeting:0x4";
$properties["replytime"] = "PT_SYSTIME:PSETID_Appointment:0x8220";
$properties["usetnef"] = "PT_BOOLEAN:PSETID_Common:0x8582";
$properties["recurrence_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
$properties["reminderminutes"] = "PT_LONG:PSETID_Common:0x8501";
$properties["reminderset"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["sendasical"] = "PT_BOOLEAN:PSETID_Appointment:0x8200";
$properties["updatecounter"] = "PT_LONG:PSETID_Appointment:0x8201"; // AppointmentSequenceNumber
$properties["last_updatecounter"] = "PT_LONG:PSETID_Appointment:0x8203"; // AppointmentLastSequence
$properties["unknown7"] = "PT_LONG:PSETID_Appointment:0x8202";
$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
$properties["intendedbusystatus"] = "PT_LONG:PSETID_Appointment:0x8224";
$properties["start"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
$properties["responselocation"] = "PT_STRING8:PSETID_Meeting:0x2";
$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
$properties["requestsent"] = "PT_BOOLEAN:PSETID_Appointment:0x8229"; // PidLidFInvited, MeetingRequestWasSent
$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
$properties["clipstart"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
$properties["clipend"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
$properties["start_recur_date"] = "PT_LONG:PSETID_Meeting:0xD"; // StartRecurTime
$properties["start_recur_time"] = "PT_LONG:PSETID_Meeting:0xE"; // StartRecurTime
$properties["end_recur_date"] = "PT_LONG:PSETID_Meeting:0xF"; // EndRecurDate
$properties["end_recur_time"] = "PT_LONG:PSETID_Meeting:0x10"; // EndRecurTime
$properties["is_exception"] = "PT_BOOLEAN:PSETID_Meeting:0xA"; // LID_IS_EXCEPTION
$properties["apptreplyname"] = "PT_STRING8:PSETID_Appointment:0x8230";
// Propose new time properties
$properties["proposed_start_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8250";
$properties["proposed_end_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8251";
$properties["proposed_duration"] = "PT_LONG:PSETID_Appointment:0x8256";
$properties["counter_proposal"] = "PT_BOOLEAN:PSETID_Appointment:0x8257";
$properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
$properties["meetingtype"] = "PT_LONG:PSETID_Meeting:0x26";
$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
$properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
$properties["toattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823B";
$properties["ccattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823C";
$this->proptags = getPropIdsFromStrings($store, $properties);
}
/**
* Sets the direct booking property. This is an alternative to the setting of the direct booking
* property through the constructor. However, setting it in the constructor is prefered.
* @param Boolean $directBookingSetting
*
*/
function setDirectBooking($directBookingSetting)
{
$this->enableDirectBooking = $directBookingSetting;
}
/**
* Returns TRUE if the message pointed to is an incoming meeting request and should
* therefore be replied to with doAccept or doDecline()
*/
function isMeetingRequest()
{
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request")
return true;
}
/**
* Returns TRUE if the message pointed to is a returning meeting request response
*/
function isMeetingRequestResponse()
{
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
if(isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp") === 0)
return true;
}
/**
* Returns TRUE if the message pointed to is a cancellation request
*/
function isMeetingCancellation()
{
$props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
if(isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Canceled")
return true;
}
/**
* Process an incoming meeting request response as Delegate. This will updates the appointment
* in Organiser's calendar.
* @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
* of corresponding meeting in Calendar
*/
function processMeetingRequestResponseAsDelegate()
{
if(!$this->isMeetingRequestResponse())
return;
$messageprops = mapi_getprops($this->message);
$goid2 = $messageprops[$this->proptags['goid2']];
if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
return;
// Find basedate in GlobalID(0x3), this can be a response for an occurrence
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
$delegatorStore = $this->getDelegatorStore($messageprops);
$userStore = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
if($calFolder){
$calendaritems = $this->findCalendarItems($goid2, $calFolder);
// $calendaritems now contains the ENTRYID's of all the calendar items to which
// this meeting request points.
// Open the calendar items, and update all the recipients of the calendar item that match
// the email address of the response.
if (!empty($calendaritems)) {
return $this->processResponse($userStore, $calendaritems[0], $basedate, $messageprops);
}else{
return false;
}
}
}
}
/**
* Process an incoming meeting request response. This updates the appointment
* in your calendar to show whether the user has accepted or declined.
* @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
* of corresponding meeting in Calendar
*/
function processMeetingRequestResponse()
{
if(!$this->isLocalOrganiser())
return;
if(!$this->isMeetingRequestResponse())
return;
// Get information we need from the response message
$messageprops = mapi_getprops($this->message, Array(
$this->proptags['goid'],
$this->proptags['goid2'],
PR_OWNER_APPT_ID,
PR_SENT_REPRESENTING_EMAIL_ADDRESS,
PR_SENT_REPRESENTING_NAME,
PR_SENT_REPRESENTING_ADDRTYPE,
PR_SENT_REPRESENTING_ENTRYID,
PR_MESSAGE_DELIVERY_TIME,
PR_MESSAGE_CLASS,
PR_PROCESSED,
$this->proptags['proposed_start_whole'],
$this->proptags['proposed_end_whole'],
$this->proptags['proposed_duration'],
$this->proptags['counter_proposal'],
$this->proptags['attendee_critical_change']));
$goid2 = $messageprops[$this->proptags['goid2']];
if(!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]))
return;
// Find basedate in GlobalID(0x3), this can be a response for an occurrence
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
$calendaritems = $this->findCalendarItems($goid2);
// $calendaritems now contains the ENTRYID's of all the calendar items to which
// this meeting request points.
// Open the calendar items, and update all the recipients of the calendar item that match
// the email address of the response.
if (!empty($calendaritems)) {
return $this->processResponse($this->store, $calendaritems[0], $basedate, $messageprops);
}else{
return false;
}
}
/**
* Process every incoming MeetingRequest response.This updates the appointment
* in your calendar to show whether the user has accepted or declined.
*@param resource $store contains the userStore in which the meeting is created
*@param $entryid contains the ENTRYID of the calendar items to which this meeting request points.
*@param boolean $basedate if present the create an exception
*@param array $messageprops contains m3/17/2010essage properties.
*@return entryids(storeid, parententryid, entryid, also basedate if response is occurrence) of corresponding meeting in Calendar
*/
function processResponse($store, $entryid, $basedate, $messageprops)
{
$data = array();
$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
$messageclass = $messageprops[PR_MESSAGE_CLASS];
$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
// the email address of the response.
$calendaritem = mapi_msgstore_openentry($store, $entryid);
$calendaritemProps = mapi_getprops($calendaritem, array($this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']));
$data["storeid"] = bin2hex($calendaritemProps[PR_STORE_ENTRYID]);
$data["parententryid"] = bin2hex($calendaritemProps[PR_PARENT_ENTRYID]);
$data["entryid"] = bin2hex($calendaritemProps[PR_ENTRYID]);
$data["basedate"] = $basedate;
$data["updatecounter"] = isset($calendaritemProps[$this->proptags['updatecounter']]) ? $calendaritemProps[$this->proptags['updatecounter']] : 0;
/**
* Check if meeting is updated or not in organizer's calendar
*/
$data["meeting_updated"] = $this->isMeetingUpdated();
if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
// meeting is already processed
return $data;
} else {
mapi_setprops($this->message, Array(PR_PROCESSED => true));
mapi_savechanges($this->message);
}
// if meeting is updated in organizer's calendar then we don't need to process
// old response
if($data['meeting_updated'] === true) {
return $data;
}
// If basedate is found, then create/modify exception msg and do processing
if ($basedate && $calendaritemProps[$this->proptags['recurring']]) {
$recurr = new Recurrence($store, $calendaritem);
// Copy properties from meeting request
$exception_props = mapi_getprops($this->message, array(PR_OWNER_APPT_ID,
$this->proptags['proposed_start_whole'],
$this->proptags['proposed_end_whole'],
$this->proptags['proposed_duration'],
$this->proptags['counter_proposal']
));
// Create/modify exception
if($recurr->isException($basedate)) {
$recurr->modifyException($exception_props, $basedate);
} else {
// When we are creating an exception we need copy recipients from main recurring item
$recipTable = mapi_message_getrecipienttable($calendaritem);
$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
// Retrieve actual start/due dates from calendar item.
$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
$recurr->createException($exception_props, $basedate, false, $recips);
}
mapi_message_savechanges($calendaritem);
$attach = $recurr->getExceptionAttachment($basedate);
if ($attach) {
$recurringItem = $calendaritem;
$calendaritem = mapi_attach_openobj($attach, MAPI_MODIFY);
} else {
return false;
}
}
// Get the recipients of the calendar item
$reciptable = mapi_message_getrecipienttable($calendaritem);
$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
// FIXME we should look at the updatecounter property and compare it
// to the counter in the recipient to see if this update is actually
// newer than the status in the calendar item
$found = false;
$totalrecips = 0;
$acceptedrecips = 0;
foreach($recipients as $recipient) {
$totalrecips++;
if(isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID],$senderentryid)) {
$found = true;
/**
* If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
* on the corresponding recipientRow of meeting then we ignore this response mail.
*/
if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
continue;
}
// The email address matches, update the row
$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
// If this is a counter proposal, set the proposal properties in the recipient row
if(isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]){
$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
}
mapi_message_modifyrecipients($calendaritem, MODRECIP_MODIFY, Array($recipient));
}
if(isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
$acceptedrecips++;
}
// If the recipient was not found in the original calendar item,
// then add the recpient as a new optional recipient
if(!$found) {
$recipient = Array();
$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
// If this is a counter proposal, set the proposal properties in the recipient row
if(isset($messageprops[$this->proptags['counter_proposal']])){
$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
}
mapi_message_modifyrecipients($calendaritem, MODRECIP_ADD, Array($recipient));
$totalrecips++;
if($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted)
$acceptedrecips++;
}
//TODO: Upate counter proposal number property on message
/*
If it is the first time this attendee has proposed a new date/time, increment the value of the PidLidAppointmentProposalNumber property on the organizer<65>s meeting object, by 0x00000001. If this property did not previously exist on the organizer<65>s meeting object, it MUST be set with a value of 0x00000001.
*/
// If this is a counter proposal, set the counter proposal indicator boolean
if(isset($messageprops[$this->proptags['counter_proposal']])){
$props = Array();
if($messageprops[$this->proptags['counter_proposal']]){
$props[$this->proptags['counter_proposal']] = true;
}else{
$props[$this->proptags['counter_proposal']] = false;
}
mapi_message_setprops($calendaritem, $props);
}
mapi_message_savechanges($calendaritem);
if (isset($attach)) {
mapi_message_savechanges($attach);
mapi_message_savechanges($recurringItem);
}
return $data;
}
/**
* Process an incoming meeting request cancellation. This updates the
* appointment in your calendar to show that the meeting has been cancelled.
*/
function processMeetingCancellation()
{
if($this->isLocalOrganiser())
return;
if(!$this->isMeetingCancellation())
return;
if(!$this->isInCalendar())
return;
$listProperties = $this->proptags;
$listProperties['subject'] = PR_SUBJECT;
$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
$messageprops = mapi_getprops($this->message, $listProperties);
$store = $this->store;
$goid = $messageprops[$this->proptags['goid']]; //GlobalID (0x3)
if(!isset($goid))
return;
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
$delegatorStore = $this->getDelegatorStore($messageprops);
$store = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
}
// First, find the items in the calendar by GOID
$calendaritems = $this->findCalendarItems($goid, $calFolder);
$basedate = $this->getBasedateFromGlobalID($goid);
if ($basedate) {
// Calendaritems with GlobalID were not found, so find main recurring item using CleanGlobalID(0x23)
if (empty($calendaritems)) {
// This meeting req is of an occurrance
$goid2 = $messageprops[$this->proptags['goid2']];
// First, find the items in the calendar by GOID
$calendaritems = $this->findCalendarItems($goid2);
foreach($calendaritems as $entryid) {
// Open each calendar item and set the properties of the cancellation object
$calendaritem = mapi_msgstore_openentry($store, $entryid);
if ($calendaritem){
$calendaritemProps = mapi_getprops($calendaritem, array($this->proptags['recurring']));
if ($calendaritemProps[$this->proptags['recurring']]){
$recurr = new Recurrence($store, $calendaritem);
// Set message class
$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
if($recurr->isException($basedate))
$recurr->modifyException($messageprops, $basedate);
else
$recurr->createException($messageprops, $basedate);
}
mapi_savechanges($calendaritem);
}
}
}
}
if (!isset($calendaritem)) {
foreach($calendaritems as $entryid) {
// Open each calendar item and set the properties of the cancellation object
$calendaritem = mapi_msgstore_openentry($store, $entryid);
mapi_message_setprops($calendaritem, $messageprops);
mapi_savechanges($calendaritem);
}
}
}
/**
* Returns true if the item is already in the calendar
*/
function isInCalendar() {
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
$goid = $messageprops[$this->proptags['goid']];
if (isset($messageprops[$this->proptags['goid2']]))
$goid2 = $messageprops[$this->proptags['goid2']];
$basedate = $this->getBasedateFromGlobalID($goid);
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])){
$delegatorStore = $this->getDelegatorStore($messageprops);
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
}
/**
* If basedate is found in globalID, then there are two possibilities.
* case 1) User has only this occurrence OR
* case 2) User has recurring item and has received an update for an occurrence
*/
if ($basedate) {
// First try with GlobalID(0x3) (case 1)
$entryid = $this->findCalendarItems($goid, $calFolder);
// If not found then try with CleanGlobalID(0x23) (case 2)
if (!is_array($entryid) && isset($goid2))
$entryid = $this->findCalendarItems($goid2, $calFolder);
} else if (isset($goid2)) {
$entryid = $this->findCalendarItems($goid2, $calFolder);
}
else
return false;
return is_array($entryid);
}
/**
* Accepts the meeting request by moving the item to the calendar
* and sending a confirmation message back to the sender. If $tentative
* is TRUE, then the item is accepted tentatively. After accepting, you
* can't use this class instance any more. The message is closed. If you
* specify TRUE for 'move', then the item is actually moved (from your
* inbox probably) to the calendar. If you don't, it is copied into
* your calendar.
*@param boolean $tentative true if user as tentative accepted the meeting
*@param boolean $sendresponse true if a response has to be send to organizer
*@param boolean $move true if the meeting request should be moved to the deleted items after processing
*@param string $newProposedStartTime contains starttime if user has proposed other time
*@param string $newProposedEndTime contains endtime if user has proposed other time
*@param string $basedate start of day of occurrence for which user has accepted the recurrent meeting
*@return string $entryid entryid of item which created/updated in calendar
*/
function doAccept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store=false, $basedate = false)
{
if($this->isLocalOrganiser())
return false;
// Remove any previous calendar items with this goid and appt id
$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));
/**
* if this function is called automatically with meeting request object then there will be
* two possibilitites
* 1) meeting request is opened first time, in this case make a tentative appointment in
recipient's calendar
* 2) after this every subsequest request to open meeting request will not do any processing
*/
if($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request" && $userAction == false) {
if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
// if meeting request is already processed then don't do anything
return false;
} else {
mapi_setprops($this->message, Array(PR_PROCESSED => true));
mapi_message_savechanges($this->message);
}
}
// If this meeting request is received by a delegate then open delegator's store.
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
$delegatorStore = $this->getDelegatorStore($messageprops);
$store = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
$store = $this->store;
}
return $this->accept($tentative, $sendresponse, $move, $newProposedStartTime, $newProposedEndTime, $body, $userAction, $store, $calFolder, $basedate);
}
function accept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store, $calFolder, $basedate = false)
{
$messageprops = mapi_getprops($this->message);
$isDelegate = false;
if (isset($messageprops[PR_DELEGATED_BY_RULE]))
$isDelegate = true;
$goid = $messageprops[$this->proptags['goid2']];
// Retrieve basedate from globalID, if it is not recieved as argument
if (!$basedate)
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
if ($sendresponse)
$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $newProposedStartTime, $newProposedEndTime, $body, $store, $basedate, $calFolder);
$entryids = $this->findCalendarItems($goid, $calFolder);
if(is_array($entryids)) {
// Only check the first, there should only be one anyway...
$previtem = mapi_msgstore_openentry($store, $entryids[0]);
$prevcounterprops = mapi_getprops($previtem, array($this->proptags['updatecounter']));
// 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
// meeting request is out of date.
/*
if(message_counter < appointment_counter) do_nothing
if(message_counter == appointment_counter) do_something_if_the_user_tells_us (userAction == true)
if(message_counter > appointment_counter) do_something_even_automatically
*/
if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] < $prevcounterprops[$this->proptags['updatecounter']]) {
return false;
} else if(isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] == $prevcounterprops[$this->proptags['updatecounter']]) {
if($userAction == false && !$basedate) {
return false;
}
}
}
// set counter proposal properties in calendar item when proposing new time
// @FIXME this can be moved before call to createResponse function so that function doesn't need to recalculate duration
$proposeNewTimeProps = array();
if($newProposedStartTime && $newProposedEndTime) {
$proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
$proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
$proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
$proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
}
/**
* Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
* 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.
* 2) If single occurrence then find occurrence itself using globalID and if item is not found then user cleanGlobalID to find main recurring item
* 3) Normal meeting req are handled normally has they were handled previously.
*
* 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
* 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.
* If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
*/
if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
// While processing the item mark it as read.
mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
// This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
if ($messageprops[$this->proptags['recurring']] == true) {
$calendarItem = false;
// Find main recurring item based on GlobalID (0x3)
$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
if (is_array($items)) {
foreach($items as $key => $entryid)
$calendarItem = mapi_msgstore_openentry($store, $entryid);
}
// Recurring item not found, so create new meeting in Calendar
if (!$calendarItem)
$calendarItem = mapi_folder_createmessage($calFolder);
// Copy properties
$props = mapi_getprops($this->message);
$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
$props[$this->proptags['meetingstatus']] = olMeetingReceived;
// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
if (isset($props[$this->proptags['intendedbusystatus']])) {
if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
$props[$this->proptags['busystatus']] = $tentative;
} else {
$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
}
// we already have intendedbusystatus value in $props so no need to copy it
} else {
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
}
if($userAction) {
// if user has responded then set replytime
$props[$this->proptags['replytime']] = time();
}
mapi_setprops($calendarItem, $props);
// Copy attachments too
$this->replaceAttachments($this->message, $calendarItem);
// Copy recipients too
$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
// Find all occurrences based on CleanGlobalID (0x23)
$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
if (is_array($items)) {
// Save all existing occurrence as exceptions
foreach($items as $entryid) {
// Open occurrence
$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
// Save occurrence into main recurring item as exception
if ($occurrenceItem) {
$occurrenceItemProps = mapi_getprops($occurrenceItem, array($this->proptags['goid'], $this->proptags['recurring']));
// Find basedate of occurrence item
$basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true)
$this->acceptException($calendarItem, $occurrenceItem, $basedate, true, $tentative, $userAction, $store, $isDelegate);
}
}
}
mapi_savechanges($calendarItem);
if ($move) {
$wastebasket = $this->openDefaultWastebasket();
mapi_folder_copymessages($calFolder, Array($props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
}
$entryid = $props[PR_ENTRYID];
} else {
/**
* This meeting request is not recurring, so can be an exception or normal meeting.
* If exception then find main recurring item and update exception
* If main recurring item is not found then put exception into Calendar as normal meeting.
*/
$calendarItem = false;
// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
if ($basedate) {
// Find main recurring item from CleanGlobalID of this meeting request
$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
if (is_array($items)) {
foreach($items as $key => $entryid) {
$calendarItem = mapi_msgstore_openentry($store, $entryid);
}
}
// Main recurring item is found, so now update exception
if ($calendarItem) {
$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
$calendarItemProps = mapi_getprops($calendarItem, array(PR_ENTRYID));
$entryid = $calendarItemProps[PR_ENTRYID];
}
}
if (!$calendarItem) {
$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
if (is_array($items))
mapi_folder_deletemessages($calFolder, $items);
if ($move) {
// All we have to do is open the default calendar,
// set the mesage class correctly to be an appointment item
// and move it to the calendar folder
$sourcefolder = $this->openParentFolder();
/* create a new calendar message, and copy the message to there,
since we want to delete (move to wastebasket) the original message */
$old_entryid = mapi_getprops($this->message, Array(PR_ENTRYID));
$calmsg = mapi_folder_createmessage($calFolder);
mapi_copyto($this->message, array(), array(), $calmsg); /* includes attachments and recipients */
/* release old message */
$message = null;
$calItemProps = Array();
$calItemProps[PR_MESSAGE_CLASS] = "IPM.Appointment";
if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
$calItemProps[$this->proptags['busystatus']] = $tentative;
} else {
$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
}
$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
} else {
$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
}
// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
if($userAction) {
// if user has responded then set replytime
$calItemProps[$this->proptags['replytime']] = time();
}
mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
// get properties which stores owner information in meeting request mails
$props = mapi_getprops($calmsg, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE));
// add owner to recipient table
$recips = array();
$this->addOrganizer($props, $recips);
if($isDelegate) {
/**
* If user is delegate then remove that user from recipienttable of the MR.
* and delegate MR mail doesn't contain any of the attendees in recipient table.
* So, other required and optional attendees are added from
* toattendeesstring and ccattendeesstring properties.
*/
$this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
$this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
mapi_message_modifyrecipients($calmsg, 0, $recips);
} else {
mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
}
mapi_message_savechanges($calmsg);
// Move the message to the wastebasket
$wastebasket = $this->openDefaultWastebasket();
mapi_folder_copymessages($sourcefolder, array($old_entryid[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
$messageprops = mapi_getprops($calmsg, array(PR_ENTRYID));
$entryid = $messageprops[PR_ENTRYID];
} else {
// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
$new = mapi_folder_createmessage($calFolder);
$props = mapi_getprops($this->message);
$props[PR_MESSAGE_CLASS] = "IPM.Appointment";
// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
if (isset($props[$this->proptags['intendedbusystatus']])) {
if($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
$props[$this->proptags['busystatus']] = $tentative;
} else {
$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
}
// we already have intendedbusystatus value in $props so no need to copy it
} else {
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
}
// ZP-341 - we need to copy as well the attachments
// Copy attachments too
$this->replaceAttachments($this->message, $new);
// ZP-341 - end
if($userAction) {
// if user has responded then set replytime
$props[$this->proptags['replytime']] = time();
}
mapi_setprops($new, $proposeNewTimeProps + $props);
$reciptable = mapi_message_getrecipienttable($this->message);
$recips = array();
if(!$isDelegate)
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
$this->addOrganizer($props, $recips);
if($isDelegate) {
/**
* If user is delegate then remove that user from recipienttable of the MR.
* and delegate MR mail doesn't contain any of the attendees in recipient table.
* So, other required and optional attendees are added from
* toattendeesstring and ccattendeesstring properties.
*/
$this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
$this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
mapi_message_modifyrecipients($new, 0, $recips);
} else {
mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
}
mapi_message_savechanges($new);
$props = mapi_getprops($new, array(PR_ENTRYID));
$entryid = $props[PR_ENTRYID];
}
}
}
} else {
// Here only properties are set on calendaritem, because user is responding from calendar.
$props = array();
$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
if($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
$props[$this->proptags['busystatus']] = $tentative;
} else {
$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
}
$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
} else {
$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
}
$props[$this->proptags['meetingstatus']] = olMeetingReceived;
$props[$this->proptags['replytime']] = time();
if ($basedate) {
$recurr = new Recurrence($store, $this->message);
// Copy recipients list
$reciptable = mapi_message_getrecipienttable($this->message);
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
if($recurr->isException($basedate)) {
$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
} else {
$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
}
} else {
mapi_setprops($this->message, $proposeNewTimeProps + $props);
}
mapi_savechanges($this->message);
$entryid = $messageprops[PR_ENTRYID];
}
return $entryid;
}
/**
* Declines the meeting request by moving the item to the deleted
* items folder and sending a decline message. After declining, you
* can't use this class instance any more. The message is closed.
* When an occurrence is decline then false is returned because that
* occurrence is deleted not the recurring item.
*
*@param boolean $sendresponse true if a response has to be sent to organizer
*@param resource $store MAPI_store of user
*@param string $basedate if specified contains starttime of day of an occurrence
*@return boolean true if item is deleted from Calendar else false
*/
function doDecline($sendresponse, $store=false, $basedate = false, $body = false)
{
$result = true;
$calendaritem = false;
if($this->isLocalOrganiser())
return;
// Remove any previous calendar items with this goid and appt id
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
// If this meeting request is received by a delegate then open delegator's store.
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
$delegatorStore = $this->getDelegatorStore($messageprops);
$store = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
$store = $this->store;
}
$goid = $messageprops[$this->proptags['goid']];
// First, find the items in the calendar by GlobalObjid (0x3)
$entryids = $this->findCalendarItems($goid, $calFolder);
if (!$basedate)
$basedate = $this->getBasedateFromGlobalID($goid);
if($sendresponse)
$this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
if ($basedate) {
// use CleanGlobalObjid (0x23)
$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
foreach($calendaritems as $entryid) {
// Open each calendar item and set the properties of the cancellation object
$calendaritem = mapi_msgstore_openentry($store, $entryid);
// Recurring item is found, now delete exception
if ($calendaritem)
$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
}
if ($this->isMeetingRequest())
$calendaritem = false;
else
$result = false;
}
if (!$calendaritem) {
$calendar = $this->openDefaultCalendar();
if(!empty($entryids)) {
mapi_folder_deletemessages($calendar, $entryids);
}
// All we have to do to decline, is to move the item to the waste basket
$wastebasket = $this->openDefaultWastebasket();
$sourcefolder = $this->openParentFolder();
$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
// Release the message
$this->message = null;
// Move the message to the waste basket
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
}
return $result;
}
/**
* Removes a meeting request from the calendar when the user presses the
* 'remove from calendar' button in response to a meeting cancellation.
* @param string $basedate if specified contains starttime of day of an occurrence
*/
function doRemoveFromCalendar($basedate)
{
if($this->isLocalOrganiser())
return false;
$store = $this->store;
$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_NAME, PR_MESSAGE_CLASS));
$goid = $messageprops[$this->proptags['goid']];
if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
$delegatorStore = $this->getDelegatorStore($messageprops);
$store = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
}
$wastebasket = $this->openDefaultWastebasket();
$sourcefolder = $this->openParentFolder();
// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
if (strpos($messageprops[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') === 0) {
/**
* 'Remove from calendar' option from previewpane then we have to check GlobalID of this meeting request.
* If basedate found then open meeting from calendar and delete that occurence.
*/
$basedate = false;
if ($goid) {
// Retrieve GlobalID and find basedate in it.
$basedate = $this->getBasedateFromGlobalID($goid);
// Basedate found, Now find item.
if ($basedate) {
$guid = $this->setBasedateInGlobalID($goid);
// First, find the items in the calendar by GOID
$calendaritems = $this->findCalendarItems($guid, $calFolder);
if(is_array($calendaritems)) {
foreach($calendaritems as $entryid) {
// Open each calendar item and set the properties of the cancellation object
$calendaritem = mapi_msgstore_openentry($store, $entryid);
if ($calendaritem){
$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
}
}
}
}
}
// It is normal/recurring meeting item.
if (!$basedate) {
if (!isset($calFolder)) $calFolder = $this->openDefaultCalendar();
$entryids = $this->findCalendarItems($goid, $calFolder);
if(is_array($entryids)){
// Move the calendaritem to the waste basket
mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
}
}
// Release the message
$this->message = null;
// Move the message to the waste basket
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
} else {
// Here only properties are set on calendaritem, because user is responding from calendar.
if ($basedate) { //remove the occurence
$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
} else { //remove normal/recurring meeting item.
// Move the message to the waste basket
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
}
}
}
/**
* Removes the meeting request by moving the item to the deleted
* items folder. After canceling, youcan't use this class instance
* any more. The message is closed.
*/
function doCancel()
{
if($this->isLocalOrganiser())
return;
if(!$this->isMeetingCancellation())
return;
// Remove any previous calendar items with this goid and appt id
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
$goid = $messageprops[$this->proptags['goid']];
$entryids = $this->findCalendarItems($goid);
$calendar = $this->openDefaultCalendar();
mapi_folder_deletemessages($calendar, $entryids);
// All we have to do to decline, is to move the item to the waste basket
$wastebasket = $this->openDefaultWastebasket();
$sourcefolder = $this->openParentFolder();
$messageprops = mapi_getprops($this->message, Array(PR_ENTRYID));
// Release the message
$this->message = null;
// Move the message to the waste basket
mapi_folder_copymessages($sourcefolder, Array($messageprops[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
}
/**
* Sets the properties in the message so that is can be sent
* as a meeting request. The caller has to submit the message. This
* is only used for new MeetingRequests. Pass the appointment item as $message
* in the constructor to do this.
*/
function setMeetingRequest($basedate = false)
{
$props = mapi_getprops($this->message, Array($this->proptags['updatecounter']));
// Create a new global id for this item
$goid = pack("H*", "040000008200E00074C5B7101A82E00800000000");
for ($i=0; $i<36; $i++)
$goid .= chr(rand(0, 255));
// Create a new appointment id for this item
$apptid = rand();
$props[PR_OWNER_APPT_ID] = $apptid;
$props[PR_ICON_INDEX] = 1026;
$props[$this->proptags['goid']] = $goid;
$props[$this->proptags['goid2']] = $goid;
if (!isset($props[$this->proptags['updatecounter']])) {
$props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero.
$props[$this->proptags['last_updatecounter']] = 0;
}
mapi_setprops($this->message, $props);
}
/**
* Sends a meeting request by copying it to the outbox, converting
* the message class, adding some properties that are required only
* for sending the message and submitting the message. Set cancel to
* true if you wish to completely cancel the meeting request. You can
* specify an optional 'prefix' to prefix the sent message, which is normally
* 'Canceled: '
*/
function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false)
{
$this->includesResources = false;
$this->nonAcceptingResources = Array();
// Get the properties of the message
$messageprops = mapi_getprops($this->message, Array($this->proptags['recurring']));
/*****************************************************************************************
* Submit message to non-resource recipients
*/
// Set BusyStatus to olTentative (1)
// Set MeetingStatus to olMeetingReceived
// Set ResponseStatus to olResponseNotResponded
/**
* While sending recurrence meeting exceptions are not send as attachments
* because first all exceptions are send and then recurrence meeting is sent.
*/
if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
// Book resource
$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
if (!$this->errorSetResource) {
$recurr = new Recurrence($this->openDefaultStore(), $this->message);
// First send meetingrequest for recurring item
$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
// Then send all meeting request for all exceptions
$exceptions = $recurr->getAllExceptions();
if ($exceptions) {
foreach($exceptions as $exceptionBasedate) {
$attach = $recurr->getExceptionAttachment($exceptionBasedate);
if ($attach) {
$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
mapi_savechanges($attach);
}
}
}
}
} else {
// Basedate found, an exception is to be send
if ($basedate) {
$recurr = new Recurrence($this->openDefaultStore(), $this->message);
if ($cancel) {
//@TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
} else {
$attach = $recurr->getExceptionAttachment($basedate);
if ($attach) {
$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
// Book resource for this occurrence
$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
if (!$this->errorSetResource) {
// Save all previous changes
mapi_savechanges($this->message);
$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
mapi_savechanges($occurrenceItem);
mapi_savechanges($attach);
}
}
}
} else {
// This is normal meeting
$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
if (!$this->errorSetResource) {
$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
}
}
}
if(isset($this->errorSetResource) && $this->errorSetResource){
return Array(
'error' => $this->errorSetResource,
'displayname' => $this->recipientDisplayname
);
}else{
return true;
}
}
function getFreeBusyInfo($entryID,$start,$end)
{
$result = array();
$fbsupport = mapi_freebusysupport_open($this->session);
if(mapi_last_hresult() != NOERROR) {
if(function_exists("dump")) {
dump("Error in opening freebusysupport object.");
}
return $result;
}
$fbDataArray = mapi_freebusysupport_loaddata($fbsupport, array($entryID));
if($fbDataArray[0] != NULL){
foreach($fbDataArray as $fbDataUser){
$rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
if($rangeuser1 == NULL){
return $result;
}
$enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
mapi_freebusyenumblock_reset($enumblock);
while(true){
$blocks = mapi_freebusyenumblock_next($enumblock, 100);
if(!$blocks){
break;
}
foreach($blocks as $blockItem){
$result[] = $blockItem;
}
}
}
}
mapi_freebusysupport_close($fbsupport);
return $result;
}
/**
* Updates the message after an update has been performed (for example,
* changing the time of the meeting). This must be called before re-sending
* the meeting request. You can also call this function instead of 'setMeetingRequest()'
* as it will automatically call setMeetingRequest on this object if it is the first
* call to this function.
*/
function updateMeetingRequest($basedate = false)
{
$messageprops = mapi_getprops($this->message, Array($this->proptags['last_updatecounter'], $this->proptags['goid']));
if(!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
$this->setMeetingRequest($basedate);
} else {
$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
// increment value of last_updatecounter, last_updatecounter will be common for recurring series
// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
// this way we can make sure that everytime we will be using a uniwue number for every operation
mapi_setprops($this->message, Array($this->proptags['last_updatecounter'] => $counter));
}
}
/**
* Returns TRUE if we are the organiser of the meeting.
*/
function isLocalOrganiser()
{
if($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
$messageid = $this->getAppointmentEntryID();
if(!isset($messageid))
return false;
$message = mapi_msgstore_openentry($this->store, $messageid);
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid']));
$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
if ($basedate) {
$recurr = new Recurrence($this->store, $message);
$attach = $recurr->getExceptionAttachment($basedate);
if ($attach) {
$occurItem = mapi_attach_openobj($attach);
$occurItemProps = mapi_getprops($occurItem, Array($this->proptags['responsestatus']));
}
}
$messageprops = mapi_getprops($message, Array($this->proptags['responsestatus']));
}
/**
* User can send recurring meeting or any occurrences from a recurring appointment so
* to be organizer 'responseStatus' property should be 'olResponseOrganized' on either
* of the recurring item or occurrence item.
*/
if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized)
|| (isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized))
return true;
else
return false;
}
/**
* Returns the entryid of the appointment that this message points at. This is
* only used on messages that are not in the calendar.
*/
function getAppointmentEntryID()
{
$messageprops = mapi_getprops($this->message, Array($this->proptags['goid2']));
$goid2 = $messageprops[$this->proptags['goid2']];
$items = $this->findCalendarItems($goid2);
if(empty($items))
return;
// There should be just one item. If there are more, we just take the first one
return $items[0];
}
/***************************************************************************************************
* Support functions - INTERNAL ONLY
***************************************************************************************************
*/
/**
* Return the tracking status of a recipient based on the IPM class (passed)
*/
function getTrackStatus($class) {
$status = olRecipientTrackStatusNone;
switch($class)
{
case "IPM.Schedule.Meeting.Resp.Pos":
$status = olRecipientTrackStatusAccepted;
break;
case "IPM.Schedule.Meeting.Resp.Tent":
$status = olRecipientTrackStatusTentative;
break;
case "IPM.Schedule.Meeting.Resp.Neg":
$status = olRecipientTrackStatusDeclined;
break;
}
return $status;
}
function openParentFolder() {
$messageprops = mapi_getprops($this->message, Array(PR_PARENT_ENTRYID));
$parentfolder = mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
return $parentfolder;
}
function openDefaultCalendar() {
return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
}
function openDefaultOutbox($store=false) {
return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
}
function openDefaultWastebasket() {
return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
}
function getDefaultWastebasketEntryID() {
return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
}
function getDefaultSentmailEntryID($store=false) {
return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
}
function getDefaultFolderEntryID($prop) {
try {
$inbox = mapi_msgstore_getreceivefolder($this->store);
} catch (MAPIException $e) {
// public store doesn't support this method
if($e->getCode() == MAPI_E_NO_SUPPORT) {
// don't propogate this error to parent handlers, if store doesn't support it
$e->setHandled();
return;
}
}
$inboxprops = mapi_getprops($inbox, Array($prop));
if(!isset($inboxprops[$prop]))
return;
return $inboxprops[$prop];
}
function openDefaultFolder($prop) {
$entryid = $this->getDefaultFolderEntryID($prop);
$folder = mapi_msgstore_openentry($this->store, $entryid);
return $folder;
}
function getBaseEntryID($prop, $store=false) {
$storeprops = mapi_getprops( (($store)?$store:$this->store) , Array($prop));
if(!isset($storeprops[$prop]))
return;
return $storeprops[$prop];
}
function openBaseFolder($prop, $store=false) {
$entryid = $this->getBaseEntryID($prop, $store);
$folder = mapi_msgstore_openentry( (($store)?$store:$this->store) , $entryid);
return $folder;
}
/**
* Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
*@param integer $status response status of attendee
*@param integer $proposalStartTime proposed starttime by attendee
*@param integer $proposalEndTime proposed endtime by attendee
*@param integer $basedate date of occurrence which attendee has responded
*/
function createResponse($status, $proposalStartTime=false, $proposalEndTime=false, $body=false, $store, $basedate = false, $calFolder) {
$messageprops = mapi_getprops($this->message, Array(PR_SENT_REPRESENTING_ENTRYID,
PR_SENT_REPRESENTING_EMAIL_ADDRESS,
PR_SENT_REPRESENTING_ADDRTYPE,
PR_SENT_REPRESENTING_NAME,
$this->proptags['goid'],
$this->proptags['goid2'],
$this->proptags['location'],
$this->proptags['startdate'],
$this->proptags['duedate'],
$this->proptags['recurring'],
$this->proptags['recurring_pattern'],
$this->proptags['recurrence_data'],
$this->proptags['timezone_data'],
$this->proptags['timezone'],
$this->proptags['updatecounter'],
PR_SUBJECT,
PR_MESSAGE_CLASS,
PR_OWNER_APPT_ID,
$this->proptags['is_exception']
));
if ($basedate && $messageprops[PR_MESSAGE_CLASS] != "IPM.Schedule.Meeting.Request" ){
// we are creating response from a recurring calendar item object
// We found basedate,so opened occurrence and get properties.
$recurr = new Recurrence($store, $this->message);
$exception = $recurr->getExceptionAttachment($basedate);
if ($exception) {
// Exception found, Now retrieve properties
$imessage = mapi_attach_openobj($exception, 0);
$imsgprops = mapi_getprops($imessage);
// If location is provided, copy it to the response
if (isset($imsgprops[$this->proptags['location']])) {
$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
}
// Update $messageprops with timings of occurrence
$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
// Meeting related properties
$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
} else {
// Exceptions is deleted.
// Update $messageprops with timings of occurrence
$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
$props[$this->proptags['meetingstatus']] = olNonMeeting;
$props[$this->proptags['responsestatus']] = olResponseNone;
}
$props[$this->proptags['recurring']] = false;
$props[$this->proptags['is_exception']] = true;
} else {
// we are creating a response from meeting request mail (it could be recurring or non-recurring)
// Send all recurrence info in response, if this is a recurrence meeting.
$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
if ($isRecurring || $isException) {
if($isRecurring) {
$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
}
if($isException) {
$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
}
$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
$calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
$recurr = new Recurrence($store, $calendaritem);
}
}
// we are sending a response for recurring meeting request (or exception), so set some required properties
if(isset($recurr) && $recurr) {
if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
}
if(!empty($messageprops[$this->proptags['recurrence_data']])) {
$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
}
$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
$this->generateRecurDates($recurr, $messageprops, $props);
}
// Create a response message
$recip = Array();
$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
switch($status) {
case olResponseAccepted:
$classpostfix = "Pos";
$subjectprefix = _("Accepted");
break;
case olResponseDeclined:
$classpostfix = "Neg";
$subjectprefix = _("Declined");
break;
case olResponseTentative:
$classpostfix = "Tent";
$subjectprefix = _("Tentatively accepted");
break;
}
if($proposalStartTime && $proposalEndTime){
// if attendee has proposed new time then change subject prefix
$subjectprefix = _("New Time Proposed");
}
$props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
$props[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Resp." . $classpostfix;
if(isset($messageprops[PR_OWNER_APPT_ID]))
$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
// Set GLOBALID AND CLEANGLOBALID, if exception then also set basedate into GLOBALID(0x3).
$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
$props[$this->proptags['updatecounter']] = $messageprops[$this->proptags['updatecounter']];
// get the default store, in which we have to store the accepted email by delegate or normal user.
$defaultStore = $this->openDefaultStore();
$props[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($defaultStore);
if($proposalStartTime && $proposalEndTime){
$props[$this->proptags['proposed_start_whole']] = $proposalStartTime;
$props[$this->proptags['proposed_end_whole']] = $proposalEndTime;
$props[$this->proptags['proposed_duration']] = round($proposalEndTime - $proposalStartTime)/60;
$props[$this->proptags['counter_proposal']] = true;
}
//Set body message in Appointment
if(isset($body)) {
$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
}
// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
// Set startdate and duedate in response mail.
$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
// responselocation is used in the UI in Outlook on the response message
if (isset($messageprops[$this->proptags['location']])) {
$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
}
// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
if(isset($store) && isset($defaultStore)) {
$storeProps = mapi_getprops($store, array(PR_ENTRYID));
$defaultStoreProps = mapi_getprops($defaultStore, array(PR_ENTRYID));
if($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]){
// get the properties of the other user (for which the logged in user is a delegate).
$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
$addrbook = mapi_openaddressbook($this->session);
$addrbookitem = mapi_ab_openentry($addrbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
$addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
// setting the following properties will ensure that the delegation part of message.
$props[PR_SENT_REPRESENTING_ENTRYID] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
$props[PR_SENT_REPRESENTING_NAME] = $addrbookitemprops[PR_DISPLAY_NAME];
$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $addrbookitemprops[PR_EMAIL_ADDRESS];
$props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
// get the properties of default store and set it accordingly
$defaultStoreProps = mapi_getprops($defaultStore, array(PR_MAILBOX_OWNER_ENTRYID));
$addrbookitem = mapi_ab_openentry($addrbook, $defaultStoreProps[PR_MAILBOX_OWNER_ENTRYID]);
$addrbookitemprops = mapi_getprops($addrbookitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
// set the following properties will ensure the sender's details, which will be the default user in this case.
//the function returns array($name, $emailaddr, $addrtype, $entryid, $searchkey);
$defaultUserDetails = $this->getOwnerAddress($defaultStore);
$props[PR_SENDER_ENTRYID] = $defaultUserDetails[3];
$props[PR_SENDER_EMAIL_ADDRESS] = $defaultUserDetails[1];
$props[PR_SENDER_NAME] = $defaultUserDetails[0];
$props[PR_SENDER_ADDRTYPE] = $defaultUserDetails[2];
}
}
// pass the default store to get the required store.
$outbox = $this->openDefaultOutbox($defaultStore);
$message = mapi_folder_createmessage($outbox);
mapi_setprops($message, $props);
mapi_message_modifyrecipients($message, MODRECIP_ADD, Array($recip));
mapi_message_savechanges($message);
mapi_message_submitmessage($message);
}
/**
* Function which finds items in calendar based on specified parameters.
*@param binary $goid GlobalID(0x3) of item
*@param resource $calendar MAPI_folder of user
*@param boolean $use_cleanGlobalID if true then search should be performed on cleanGlobalID(0x23) else globalID(0x3)
*/
function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
if(!$calendar) {
// Open the Calendar
$calendar = $this->openDefaultCalendar();
}
// Find the item by restricting all items to the correct ID
$restrict = Array(RES_AND, Array());
array_push($restrict[1], Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
VALUE => $goid
)
));
$calendarcontents = mapi_folder_getcontentstable($calendar);
$rows = mapi_table_queryallrows($calendarcontents, Array(PR_ENTRYID), $restrict);
if(empty($rows))
return;
$calendaritems = Array();
// In principle, there should only be one row, but we'll handle them all just in case
foreach($rows as $row) {
$calendaritems[] = $row[PR_ENTRYID];
}
return $calendaritems;
}
// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
// same SMTP address when converted to SMTP
function compareABEntryIDs($entryid1, $entryid2) {
// If the session was not passed, just do a 'normal' compare.
if(!$this->session)
return $entryid1 == $entryid2;
$smtp1 = $this->getSMTPAddress($entryid1);
$smtp2 = $this->getSMTPAddress($entryid2);
if($smtp1 == $smtp2)
return true;
else
return false;
}
// Gets the SMTP address of the passed addressbook entryid
function getSMTPAddress($entryid) {
if(!$this->session)
return false;
$ab = mapi_openaddressbook($this->session);
$abitem = mapi_ab_openentry($ab, $entryid);
if(!$abitem)
return "";
$props = mapi_getprops($abitem, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
if($props[PR_ADDRTYPE] == "SMTP") {
return $props[PR_EMAIL_ADDRESS];
}
else return $props[PR_SMTP_ADDRESS];
}
/**
* Gets the properties associated with the owner of the passed store:
* PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY
*
* @param $store message store
* @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
* not used when passed store is public store. for public store we are always returning logged in user's info.
* @return properties of logged in user in an array in sequence of display_name, email address, address tyep,
* entryid and search key.
*/
function getOwnerAddress($store, $fallbackToLoggedInUser = true)
{
if(!$this->session)
return false;
$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID));
$ownerEntryId = false;
if(isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
$ownerEntryId = $storeProps[PR_USER_ENTRYID];
}
if(isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
}
if($ownerEntryId) {
$ab = mapi_openaddressbook($this->session);
$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
if(!$zarafaUser)
return false;
$ownerProps = mapi_getprops($zarafaUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));
$addrType = $ownerProps[PR_ADDRTYPE];
$name = $ownerProps[PR_DISPLAY_NAME];
$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
$searchKey = strtoupper($addrType) . ":" . strtoupper($emailAddr);
$entryId = $ownerEntryId;
return array($name, $emailAddr, $addrType, $entryId, $searchKey);
}
return false;
}
// Opens this session's default message store
function openDefaultStore()
{
$storestable = mapi_getmsgstorestable($this->session);
$rows = mapi_table_queryallrows($storestable, array(PR_ENTRYID, PR_DEFAULT_STORE));
$entry = false;
foreach($rows as $row) {
if(isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
$entryid = $row[PR_ENTRYID];
break;
}
}
if(!$entryid)
return false;
return mapi_openmsgstore($this->session, $entryid);
}
/**
* Function which adds organizer to recipient list which is passed.
* This function also checks if it has organizer.
*
* @param array $messageProps message properties
* @param array $recipients recipients list of message.
* @param boolean $isException true if we are processing recipient of exception
*/
function addOrganizer($messageProps, &$recipients, $isException = false){
$hasOrganizer = false;
// Check if meeting already has an organizer.
foreach ($recipients as $key => $recipient){
if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
$hasOrganizer = true;
} else if ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])){
// Recipients for an occurrence
$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
}
}
if (!$hasOrganizer){
// Create organizer.
$organizer = array();
$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
// Add organizer to recipients list.
array_unshift($recipients, $organizer);
}
}
/**
* Function adds recipients in recips array from the string.
*
* @param array $recips recipient array.
* @param string $recipString recipient string attendees.
* @param int $type type of the recipient, MAPI_TO/MAPI_CC.
*/
function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO)
{
$extraRecipient = array();
$recipArray = explode(";", $recipString);
foreach($recipArray as $recip) {
$recip = trim($recip);
if (!empty($recip)) {
$extraRecipient[PR_RECIPIENT_TYPE] = $recipType;
$extraRecipient[PR_DISPLAY_NAME] = $recip;
array_push($recips, $extraRecipient);
}
}
}
/**
* Function which removes an exception/occurrence from recurrencing meeting
* when a meeting cancellation of an occurrence is processed.
*@param string $basedate basedate of an occurrence
*@param resource $message recurring item from which occurrence has to be deleted
*@param resource $store MAPI_MSG_Store which contains the item
*/
function doRemoveExceptionFromCalendar($basedate, $message, $store)
{
$recurr = new Recurrence($store, $message);
$recurr->createException(array(), $basedate, true);
mapi_savechanges($message);
}
/**
* Function which returns basedate of an changed occurrance from globalID of meeting request.
*@param binary $goid globalID
*@return boolean true if basedate is found else false it not found
*/
function getBasedateFromGlobalID($goid)
{
$hexguid = bin2hex($goid);
$hexbase = substr($hexguid, 32, 8);
$day = hexdec(substr($hexbase, 6, 2));
$month = hexdec(substr($hexbase, 4, 2));
$year = hexdec(substr($hexbase, 0, 4));
if ($day && $month && $year)
return gmmktime(0, 0, 0, $month, $day, $year);
else
return false;
}
/**
* Function which sets basedate in globalID of changed occurrance which is to be send.
*@param binary $goid globalID
*@param string basedate of changed occurrance
*@return binary globalID with basedate in it
*/
function setBasedateInGlobalID($goid, $basedate = false)
{
$hexguid = bin2hex($goid);
$year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
$month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
$day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
}
/**
* Function which replaces attachments with copy_from in copy_to.
*@param resource $copy_from MAPI_message from which attachments are to be copied.
*@param resource $copy_to MAPI_message to which attachment are to be copied.
*@param boolean $copyExceptions if true then all exceptions should also be sent as attachments
*/
function replaceAttachments($copy_from, $copy_to, $copyExceptions = true)
{
/* remove all old attachments */
$attachmentTable = mapi_message_getattachmenttable($copy_to);
if($attachmentTable) {
$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
foreach($attachments as $attach_props){
/* remove exceptions too? */
if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
continue;
mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
}
}
$attachmentTable = false;
/* copy new attachments */
$attachmentTable = mapi_message_getattachmenttable($copy_from);
if($attachmentTable) {
$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME));
foreach($attachments as $attach_props){
if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME]))
continue;
$attach_old = mapi_message_openattach($copy_from, (int) $attach_props[PR_ATTACH_NUM]);
$attach_newResourceMsg = mapi_message_createattach($copy_to);
mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
mapi_savechanges($attach_newResourceMsg);
}
}
}
/**
* Function which replaces recipients in copy_to with recipients from copy_from.
*@param resource $copy_from MAPI_message from which recipients are to be copied.
*@param resource $copy_to MAPI_message to which recipients are to be copied.
*/
function replaceRecipients($copy_from, $copy_to, $isDelegate = false)
{
$recipienttable = mapi_message_getrecipienttable($copy_from);
// If delegate, then do not add the delegate in recipients
if ($isDelegate) {
$delegate = mapi_getprops($copy_from, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
$res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $res);
} else {
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
}
$copy_to_recipientTable = mapi_message_getrecipienttable($copy_to);
$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
foreach($copy_to_recipientRows as $recipient) {
mapi_message_modifyrecipients($copy_to, MODRECIP_REMOVE, array($recipient));
}
mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
}
/**
* Function creates meeting item in resource's calendar.
*@param resource $message MAPI_message which is to create in resource's calendar
*@param boolean $cancel cancel meeting
*@param string $prefix prefix for subject of meeting
*/
function bookResources($message, $cancel, $prefix, $basedate = false)
{
if(!$this->enableDirectBooking)
return array();
// Get the properties of the message
$messageprops = mapi_getprops($message);
if ($basedate) {
$recurrItemProps = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID));
$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
// Delete properties which are not needed.
$deleteProps = array($this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD);
foreach ($deleteProps as $propID) {
if (isset($messageprops[$propID])) {
unset($messageprops[$propID]);
}
}
if (isset($messageprops[$this->proptags['recurring']])) $messageprops[$this->proptags['recurring']] = false;
// Set Outlook properties
$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
$messageprops[$this->proptags['attendee_critical_change']] = time();
$messageprops[$this->proptags['owner_critical_change']] = time();
}
// Get resource recipients
$getResourcesRestriction = Array(RES_AND,
Array(Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ, // Equals recipient type 3: Resource
ULPROPTAG => PR_RECIPIENT_TYPE,
VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
)
))
);
$recipienttable = mapi_message_getrecipienttable($message);
$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
$this->errorSetResource = false;
$resourceRecipData = Array();
// Put appointment into store resource users
$i = 0;
$len = count($resourceRecipients);
while(!$this->errorSetResource && $i < $len){
$request = array(array(PR_DISPLAY_NAME => $resourceRecipients[$i][PR_DISPLAY_NAME]));
$ab = mapi_openaddressbook($this->session);
$ret = mapi_ab_resolvename($ab, $request, EMS_AB_ADDRESS_LOOKUP);
$result = mapi_last_hresult();
if ($result == NOERROR){
$result = $ret[0][PR_ENTRYID];
}
$resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
$resourceABEntryID = $ret[0][PR_ENTRYID];
// Get StoreEntryID by username
$user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
// Open store of the user
$userStore = mapi_openmsgstore($this->session, $user_entryid);
// Open root folder
$userRoot = mapi_msgstore_openentry($userStore, null);
// Get calendar entryID
$userRootProps = mapi_getprops($userRoot, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
// Open Calendar folder [check hresult==0]
$accessToFolder = false;
try {
$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
if($calFolder){
$calFolderProps = mapi_getProps($calFolder, Array(PR_ACCESS));
if(($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0){
$accessToFolder = true;
}
}
} catch (MAPIException $e) {
$e->setHandled();
$this->errorSetResource = 1; // No access
}
if($accessToFolder) {
/**
* Get the LocalFreebusy message that contains the properties that
* are set to accept or decline resource meeting requests
*/
// Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
$localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
if($localFreebusyMsg){
$props = mapi_getprops($localFreebusyMsg, array(PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS));
$acceptMeetingRequests = ($props[PR_PROCESS_MEETING_REQUESTS])?1:0;
$declineRecurringMeetingRequests = ($props[PR_DECLINE_RECURRING_MEETING_REQUESTS])?1:0;
$declineConflictingMeetingRequests = ($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS])?1:0;
if(!$acceptMeetingRequests){
/**
* When a resource has not been set to automatically accept meeting requests,
* the meeting request has to be sent to him rather than being put directly into
* his calendar. No error should be returned.
*/
//$errorSetResource = 2;
$this->nonAcceptingResources[] = $resourceRecipients[$i];
}else{
if($declineRecurringMeetingRequests && !$cancel){
// Check if appointment is recurring
if($messageprops[ $this->proptags['recurring'] ]){
$this->errorSetResource = 3;
}
}
if($declineConflictingMeetingRequests && !$cancel){
// Check for conflicting items
$conflicting = false;
// Open the calendar
$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
if($calFolder) {
if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops))
$conflicting = true;
} else {
$this->errorSetResource = 1; // No access
}
if($conflicting){
$this->errorSetResource = 4; // Conflict
}
}
}
}
}
if(!$this->errorSetResource && $accessToFolder){
/**
* First search on GlobalID(0x3)
* 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.
* If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesnt matter if search is based on GlobalID.
*/
$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
/**
* If no entry is found then
* 1) Resource doesnt have meeting in Calendar. Seriously!!
* OR
* 2) We were looking for occurrence item but Resource has whole series
*/
if(empty($rows)){
/**
* Now search on CleanGlobalID(0x23) WHY???
* Because we are looking recurring item
*
* Possible results of this search
* 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
* 2) If Resource was booked for whole series then it should return series.
*/
$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
$newResourceMsg = false;
if (!empty($rows)) {
// Since we are looking for recurring item, open every result and check for 'recurring' property.
foreach($rows as $row) {
$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
$ResourceMsgProps = mapi_getprops($ResourceMsg, array($this->proptags['recurring']));
if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
$newResourceMsg = $ResourceMsg;
break;
}
}
}
// Still no results found. I giveup, create new message.
if (!$newResourceMsg)
$newResourceMsg = mapi_folder_createmessage($calFolder);
}else{
$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
}
// Prefix the subject if needed
if($prefix && isset($messageprops[PR_SUBJECT])) {
$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
}
// Set status to cancelled if needed
$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
if($cancel) {
$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
$messageprops[$this->proptags['busystatus']] = fbFree; // Free
} else {
$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
}
$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource autmatically accepts the appointment
$messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
// Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
// confuses the Zarafa webaccess
$messageprops[PR_ICON_INDEX] = null;
$messageprops[PR_RESPONSE_REQUESTED] = true;
$addrinfo = $this->getOwnerAddress($this->store);
if($addrinfo) {
list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
$messageprops[$this->proptags['apptreplyname']] = $ownername;
$messageprops[$this->proptags['replytime']] = time();
}
if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
$recurr = new Recurrence($userStore, $newResourceMsg);
// Copy recipients list
$reciptable = mapi_message_getrecipienttable($message);
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
// add owner to recipient table
$this->addOrganizer($messageprops, $recips, true);
// Update occurrence
if($recurr->isException($basedate))
$recurr->modifyException($messageprops, $basedate, $recips);
else
$recurr->createException($messageprops, $basedate, false, $recips);
} else {
mapi_setprops($newResourceMsg, $messageprops);
// Copy attachments
$this->replaceAttachments($message, $newResourceMsg);
// Copy all recipients too
$this->replaceRecipients($message, $newResourceMsg);
// Now add organizer also to recipient table
$recips = Array();
$this->addOrganizer($messageprops, $recips);
mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
}
mapi_savechanges($newResourceMsg);
$resourceRecipData[] = Array(
'store' => $userStore,
'folder' => $calFolder,
'msg' => $newResourceMsg,
);
$this->includesResources = true;
}else{
/**
* If no other errors occured and you have no access to the
* folder of the resource, throw an error=1.
*/
if(!$this->errorSetResource){
$this->errorSetResource = 1;
}
for($j = 0, $len = count($resourceRecipData); $j < $len; $j++){
// Get the EntryID
$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
mapi_folder_deletemessages($resourceRecipData[$j]['folder'], Array($props[PR_ENTRYID]), DELETE_HARD_DELETE);
}
$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
}
$i++;
}
/**************************************************************
* Set the BCC-recipients (resources) tackstatus to accepted.
*/
// Get resource recipients
$getResourcesRestriction = Array(RES_AND,
Array(Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ, // Equals recipient type 3: Resource
ULPROPTAG => PR_RECIPIENT_TYPE,
VALUE => array(PR_RECIPIENT_TYPE =>MAPI_BCC)
)
))
);
$recipienttable = mapi_message_getrecipienttable($message);
$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
if(!empty($resourceRecipients)){
// Set Tracking status of resource recipients to olResponseAccepted (3)
for($i = 0, $len = count($resourceRecipients); $i < $len; $i++){
$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
}
mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
}
// Publish updated free/busy information
if(!$this->errorSetResource){
for($i = 0, $len = count($resourceRecipData); $i < $len; $i++){
$storeProps = mapi_msgstore_getprops($resourceRecipData[$i]['store'], array(PR_MAILBOX_OWNER_ENTRYID));
if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])){
$pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
$pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
}
}
}
return $resourceRecipData;
}
/**
* Function which save an exception into recurring item
*
* @param resource $recurringItem reference to MAPI_message of recurring item
* @param resource $occurrenceItem reference to MAPI_message of occurrence
* @param string $basedate basedate of occurrence
* @param boolean $move if true then occurrence item is deleted
* @param boolean $tentative true if user has tentatively accepted it or false if user has accepted it.
* @param boolean $userAction true if user has manually responded to meeting request
* @param resource $store user store
* @param boolean $isDelegate true if delegate is processing this meeting request
*/
function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false)
{
$recurr = new Recurrence($store, $recurringItem);
// Copy properties from meeting request
$exception_props = mapi_getprops($occurrenceItem);
// Copy recipients list
$reciptable = mapi_message_getrecipienttable($occurrenceItem);
// If delegate, then do not add the delegate in recipients
if ($isDelegate) {
$delegate = mapi_getprops($this->message, array(PR_RECEIVED_BY_EMAIL_ADDRESS));
$res = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]));
$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
} else {
$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
}
// add owner to recipient table
$this->addOrganizer($exception_props, $recips, true);
// add delegator to meetings
if ($isDelegate) $this->addDelegator($exception_props, $recips);
$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
// Set basedate property (ExceptionReplaceTime)
if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
if($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
$exception_props[$this->proptags['busystatus']] = $tentative;
} else {
$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
}
// we already have intendedbusystatus value in $exception_props so no need to copy it
} else {
$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
}
if($userAction) {
// if user has responded then set replytime
$exception_props[$this->proptags['replytime']] = time();
}
if($recurr->isException($basedate))
$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
else
$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
// Move the occurrenceItem to the waste basket
if ($move) {
$wastebasket = $this->openDefaultWastebasket();
$sourcefolder = mapi_msgstore_openentry($this->store, $exception_props[PR_PARENT_ENTRYID]);
mapi_folder_copymessages($sourcefolder, Array($exception_props[PR_ENTRYID]), $wastebasket, MESSAGE_MOVE);
}
mapi_savechanges($recurringItem);
}
/**
* Function which submits meeting request based on arguments passed to it.
*@param resource $message MAPI_message whose meeting request is to be send
*@param boolean $cancel if true send request, else send cancellation
*@param string $prefix subject prefix
*@param integer $basedate basedate for an occurrence
*@param Object $recurObject recurrence object of mr
*@param boolean $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
*/
function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false)
{
$newmessageprops = $messageprops = mapi_getprops($this->message);
$new = $this->createOutgoingMessage();
// Copy the entire message into the new meeting request message
if ($basedate) {
// messageprops contains properties of whole recurring series
// and newmessageprops contains properties of exception item
$newmessageprops = mapi_getprops($message);
// Ensure that the correct basedate is set in the new message
$newmessageprops[$this->proptags['basedate']] = $basedate;
// Set isRecurring to false, because this is an exception
$newmessageprops[$this->proptags['recurring']] = false;
// set LID_IS_EXCEPTION to true
$newmessageprops[$this->proptags['is_exception']] = true;
// Set to high importance
if($cancel) $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
// Set startdate and enddate of exception
if ($cancel && $recurObject) {
$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
}
// Set basedate in guid (0x3)
$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
// Get deleted recipiets from exception msg
$restriction = Array(RES_AND,
Array(
Array(RES_BITMASK,
Array( ULTYPE => BMR_NEZ,
ULPROPTAG => PR_RECIPIENT_FLAGS,
ULMASK => recipExceptionalDeleted
)
),
Array(RES_BITMASK,
Array( ULTYPE => BMR_EQZ,
ULPROPTAG => PR_RECIPIENT_FLAGS,
ULMASK => recipOrganizer
)
),
)
);
// In direct-booking mode, we don't need to send cancellations to resources
if($this->enableDirectBooking) {
$restriction[1][] = Array(RES_PROPERTY,
Array(RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource)
ULPROPTAG => PR_RECIPIENT_TYPE,
VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
)
);
}
$recipienttable = mapi_message_getrecipienttable($message);
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
if (!$deletedRecips) {
$deletedRecips = array_merge(array(), $recipients);
} else {
$deletedRecips = array_merge($deletedRecips, $recipients);
}
}
// Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
// confuses the Zarafa webaccess
$newmessageprops[PR_ICON_INDEX] = null;
$newmessageprops[PR_RESPONSE_REQUESTED] = true;
// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
// Set updatecounter/AppointmentSequenceNumber
// get the value of latest updatecounter for the whole series and use it
$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
$meetingTimeInfo = $this->getMeetingTimeInfo();
if($meetingTimeInfo)
$newmessageprops[PR_BODY] = $meetingTimeInfo;
// Send all recurrence info in mail, if this is a recurrence meeting.
if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
if(!empty($messageprops[$this->proptags['recurring_pattern']])) {
$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
}
$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
if($recurObject) {
$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
}
}
if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
unset($newmessageprops[$this->proptags['counter_proposal']]);
}
// Prefix the subject if needed
if ($prefix && isset($newmessageprops[PR_SUBJECT]))
$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
mapi_setprops($new, $newmessageprops);
// Copy attachments
$this->replaceAttachments($message, $new, $copyExceptions);
// Retrieve only those recipient who should receive this meeting request.
$stripResourcesRestriction = Array(RES_AND,
Array(
Array(RES_BITMASK,
Array( ULTYPE => BMR_EQZ,
ULPROPTAG => PR_RECIPIENT_FLAGS,
ULMASK => recipExceptionalDeleted
)
),
Array(RES_BITMASK,
Array( ULTYPE => BMR_EQZ,
ULPROPTAG => PR_RECIPIENT_FLAGS,
ULMASK => recipOrganizer
)
),
)
);
// In direct-booking mode, resources do not receive a meeting request
if($this->enableDirectBooking) {
$stripResourcesRestriction[1][] =
Array(RES_PROPERTY,
Array(RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource)
ULPROPTAG => PR_RECIPIENT_TYPE,
VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC)
)
);
}
$recipienttable = mapi_message_getrecipienttable($message);
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
if ($basedate && empty($recipients)) {
// Retrieve full list
$recipienttable = mapi_message_getrecipienttable($this->message);
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
// Save recipients in exceptions
mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
// Now retrieve only those recipient who should receive this meeting request.
$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
}
//@TODO: handle nonAcceptingResources
/**
* Add resource recipients that did not automatically accept the meeting request.
* (note: meaning that they did not decline the meeting request)
*//*
for($i=0;$i<count($this->nonAcceptingResources);$i++){
$recipients[] = $this->nonAcceptingResources[$i];
}*/
if(!empty($recipients)) {
// Strip out the sender/"owner" recipient
mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
// Set some properties that are different in the sent request than
// in the item in our calendar
// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
// should always be fbTentative
$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
$newmessageprops[$this->proptags['attendee_critical_change']] = time();
$newmessageprops[$this->proptags['owner_critical_change']] = time();
$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
if ($cancel) {
$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
} else {
$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
}
mapi_setprops($new, $newmessageprops);
mapi_message_savechanges($new);
// Submit message to non-resource recipients
mapi_message_submitmessage($new);
}
// Send cancellation to deleted attendees
if ($deletedRecips && !empty($deletedRecips)) {
$new = $this->createOutgoingMessage();
mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance
if (isset($newmessageprops[PR_SUBJECT])) {
$newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
}
mapi_setprops($new, $newmessageprops);
mapi_message_savechanges($new);
// Submit message to non-resource recipients
mapi_message_submitmessage($new);
}
// Set properties on meeting object in calendar
// Set requestsent to 'true' (turns on 'tracking', etc)
$props = array();
$props[$this->proptags['meetingstatus']] = olMeeting;
$props[$this->proptags['responsestatus']] = olResponseOrganized;
$props[$this->proptags['requestsent']] = (!empty($recipients)) || ($this->includesResources && !$this->errorSetResource);
$props[$this->proptags['attendee_critical_change']] = time();
$props[$this->proptags['owner_critical_change']] = time();
$props[$this->proptags['meetingtype']] = mtgRequest;
// save the new updatecounter to exception/recurring series/normal meeting
$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
mapi_setprops($message, $props);
// saving of these properties on calendar item should be handled by caller function
// based on sending meeting request was successfull or not
}
/**
* OL2007 uses these 4 properties to specify occurence that should be updated.
* ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
* but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
* from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
* also additionally we are sending these properties.
* Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID
* @param Object $recurObject instance of recurrence class for this message
* @param Array $messageprops properties of meeting object that is going to be send
* @param Array $newmessageprops properties of meeting request/response that is going to be send
*/
function generateRecurDates($recurObject, $messageprops, &$newmessageprops)
{
if($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
$startDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
$endDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
$startDate = explode(":", $startDate);
$endDate = explode(":", $endDate);
// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
// RecurStartDate = year * 512 + month_number * 32 + day_number
$newmessageprops[$this->proptags["start_recur_date"]] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
// RecurStartTime = hour * 4096 + minutes * 64 + seconds
$newmessageprops[$this->proptags["start_recur_time"]] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
$newmessageprops[$this->proptags["end_recur_date"]] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
$newmessageprops[$this->proptags["end_recur_time"]] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
}
}
function createOutgoingMessage()
{
$sentprops = array();
$outbox = $this->openDefaultOutbox($this->openDefaultStore());
$outgoing = mapi_folder_createmessage($outbox);
if(!$outgoing) return false;
$addrinfo = $this->getOwnerAddress($this->store);
if($addrinfo) {
list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
}
$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
mapi_setprops($outgoing, $sentprops);
return $outgoing;
}
/**
* Function which checks received meeting request is either old(outofdate) or new.
* @return boolean true if meeting request is outofdate else false if it is new
*/
function isMeetingOutOfDate()
{
$result = false;
$store = $this->store;
$props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']));
if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
return true;
}
// get the basedate to check for exception
$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
$calendarItems = $this->getCorrespondedCalendarItems();
foreach($calendarItems as $calendarItem) {
if ($calendarItem) {
$calendarItemProps = mapi_getprops($calendarItem, array(
$this->proptags['owner_critical_change'],
$this->proptags['updatecounter'],
$this->proptags['recurring']
));
// If these items is recurring and basedate is found then open exception to compare it with meeting request
if (isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] && $basedate) {
$recurr = new Recurrence($store, $calendarItem);
if ($recurr->isException($basedate)) {
$attach = $recurr->getExceptionAttachment($basedate);
$exception = mapi_attach_openobj($attach, 0);
$occurrenceItemProps = mapi_getprops($exception, array(
$this->proptags['owner_critical_change'],
$this->proptags['updatecounter']
));
}
// we found the exception, compare with it
if(isset($occurrenceItemProps)) {
if ((isset($occurrenceItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $occurrenceItemProps[$this->proptags['updatecounter']])
|| (isset($occurrenceItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $occurrenceItemProps[$this->proptags['owner_critical_change']])) {
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
mapi_savechanges($this->message);
$result = true;
}
} else {
// we are not able to find exception, could mean that a significant change has occured on series
// and it deleted all exceptions, so compare with series
if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
|| (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
mapi_savechanges($this->message);
$result = true;
}
}
} else {
// normal / recurring series
if ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']])
|| (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
mapi_setprops($this->message, array($this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033));
mapi_savechanges($this->message);
$result = true;
}
}
}
}
return $result;
}
/**
* Function which checks received meeting request is updated later or not.
* @return boolean true if meeting request is updated later.
* @TODO: Implement handling for recurrings and exceptions.
*/
function isMeetingUpdated()
{
$result = false;
$store = $this->store;
$props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['owner_critical_change'], $this->proptags['updatecounter']));
$calendarItems = $this->getCorrespondedCalendarItems();
foreach($calendarItems as $calendarItem) {
if ($calendarItem) {
$calendarItemProps = mapi_getprops($calendarItem, array(
$this->proptags['updatecounter'],
$this->proptags['recurring']
));
if(isset($calendarItemProps[$this->proptags['updatecounter']]) && isset($props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
$result = true;
}
}
}
return $result;
}
/**
* Checks if there has been any significant changes on appointment/meeting item.
* Significant changes be:
* 1) startdate has been changed
* 2) duedate has been changed OR
* 3) recurrence pattern has been created, modified or removed
*
* @param Array oldProps old props before an update
* @param Number basedate basedate
* @param Boolean isRecurrenceChanged for change in recurrence pattern.
* isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
*/
function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false)
{
$message = null;
$attach = null;
// If basedate is specified then we need to open exception message to clear recipient responses
if($basedate) {
$recurrence = new Recurrence($this->store, $this->message);
if($recurrence->isException($basedate)){
$attach = $recurrence->getExceptionAttachment($basedate);
if ($attach) {
$message = mapi_attach_openobj($attach, MAPI_MODIFY);
}
}
} else {
// use normal message or recurring series message
$message = $this->message;
}
if(!$message) {
return;
}
$newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']));
// Check whether message is updated or not.
if(isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
return;
}
if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']])
|| ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']])
|| $isRecurrenceChanged) {
$this->clearRecipientResponse($message);
mapi_setprops($message, array($this->proptags['owner_critical_change'] => time()));
mapi_savechanges($message);
if ($attach) { // Also save attachment Object.
mapi_savechanges($attach);
}
}
}
/**
* Clear responses of all attendees who have replied in past.
* @param MAPI_MESSAGE $message on which responses should be cleared
*/
function clearRecipientResponse($message)
{
$recipTable = mapi_message_getrecipienttable($message);
$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
foreach($recipsRows as $recipient) {
if(($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer){
// Recipient is attendee, set the trackstatus to "Not Responded"
$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
} else {
// Recipient is organizer, this is not possible, but for safety
// it is best to clear the trackstatus for him as well by setting
// the trackstatus to "Organized".
$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
}
mapi_message_modifyrecipients($message, MODRECIP_MODIFY, array($recipient));
}
}
/**
* Function returns corresponded calendar items attached with
* the meeting request.
* @return Array array of correlated calendar items.
*/
function getCorrespondedCalendarItems()
{
$store = $this->store;
$props = mapi_getprops($this->message, array($this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME));
$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
// If Delegate is processing mr for Delegator then retrieve Delegator's store and calendar.
if (isset($props[PR_RCVD_REPRESENTING_NAME])) {
$delegatorStore = $this->getDelegatorStore($props);
$store = $delegatorStore['store'];
$calFolder = $delegatorStore['calFolder'];
} else {
$calFolder = $this->openDefaultCalendar();
}
// Finding item in calendar with GlobalID(0x3), not necessary that attendee is having recurring item, he/she can also have only a occurrence
$entryids = $this->findCalendarItems($props[$this->proptags['goid']], $calFolder);
// Basedate found, so this meeting request is an update of an occurrence.
if ($basedate) {
if (!$entryids) {
// Find main recurring item in calendar with GlobalID(0x23)
$entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
}
}
$calendarItems = array();
if ($entryids) {
foreach($entryids as $entryid) {
$calendarItems[] = mapi_msgstore_openentry($store, $entryid);
}
}
return $calendarItems;
}
/**
* Function which checks whether received meeting request is either conflicting with other appointments or not.
*@return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
* conflict of recurring meeting and false if meeting is not conflicting.
*/
function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false)
{
$returnValue = false;
$conflicting = false;
$noOfInstances = 0;
if (!$message) $message = $this->message;
if (!$userStore) $userStore = $this->store;
if (!$calFolder) {
$root = mapi_msgstore_openentry($userStore);
$rootprops = mapi_getprops($root, array(PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS));
if(!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
return;
}
$calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
}
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']));
if ($calFolder) {
// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
if (isset($msgprops[$this->proptags['recurring']]) && $msgprops[$this->proptags['recurring']]) {
// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
$recurr = new Recurrence($userStore, $message);
$items = $recurr->getItems($msgprops[$this->proptags['clipstart']], $msgprops[$this->proptags['clipend']] * (24*24*60), 30);
foreach ($items as $item) {
// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
foreach ($calendarItems as $calendarItem) {
if ($calendarItem[$this->proptags['busystatus']] != fbFree) {
/**
* Only meeting requests have globalID, normal appointments do not have globalID
* so if any normal appointment if found then it is assumed to be conflict.
*/
if(isset($calendarItem[$this->proptags['goid']])) {
if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
$noOfInstances++;
break;
}
} else {
$noOfInstances++;
break;
}
}
}
}
$returnValue = $noOfInstances;
} else {
// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
$items = getCalendarItems($userStore, $calFolder, $msgprops[$this->proptags['startdate']], $msgprops[$this->proptags['duedate']], array($this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID));
foreach($items as $item) {
if ($item[$this->proptags['busystatus']] != fbFree) {
if(isset($item[$this->proptags['goid']])) {
if (($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']])
&& ($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid2']])) {
$conflicting = true;
break;
}
} else {
$conflicting = true;
break;
}
}
}
if ($conflicting) $returnValue = true;
}
}
return $returnValue;
}
/**
* Function which adds organizer to recipient list which is passed.
* This function also checks if it has organizer.
*
* @param array $messageProps message properties
* @param array $recipients recipients list of message.
* @param boolean $isException true if we are processing recipient of exception
*/
function addDelegator($messageProps, &$recipients)
{
$hasDelegator = false;
// Check if meeting already has an organizer.
foreach ($recipients as $key => $recipient){
if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS])
$hasDelegator = true;
}
if (!$hasDelegator){
// Create delegator.
$delegator = array();
$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP':$messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
// Add organizer to recipients list.
array_unshift($recipients, $delegator);
}
}
function getDelegatorStore($messageprops)
{
// Find the organiser of appointment in addressbook
$delegatorName = array(array(PR_DISPLAY_NAME => $messageprops[PR_RCVD_REPRESENTING_NAME]));
$ab = mapi_openaddressbook($this->session);
$user = mapi_ab_resolvename($ab, $delegatorName, EMS_AB_ADDRESS_LOOKUP);
// Get StoreEntryID by username
$delegatorEntryid = mapi_msgstore_createentryid($this->store, $user[0][PR_EMAIL_ADDRESS]);
// Open store of the delegator
$delegatorStore = mapi_openmsgstore($this->session, $delegatorEntryid);
// Open root folder
$delegatorRoot = mapi_msgstore_openentry($delegatorStore, null);
// Get calendar entryID
$delegatorRootProps = mapi_getprops($delegatorRoot, array(PR_IPM_APPOINTMENT_ENTRYID));
// Open the calendar Folder
$calFolder = mapi_msgstore_openentry($delegatorStore, $delegatorRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
return Array('store' => $delegatorStore, 'calFolder' => $calFolder);
}
/**
* Function returns extra info about meeting timing along with message body
* which will be included in body while sending meeting request/response.
*
* @return string $meetingTimeInfo info about meeting timing along with message body
*/
function getMeetingTimeInfo()
{
return $this->meetingTimeInfo;
}
/**
* Function sets extra info about meeting timing along with message body
* which will be included in body while sending meeting request/response.
*
* @param string $meetingTimeInfo info about meeting timing along with message body
*/
function setMeetingTimeInfo($meetingTimeInfo)
{
$this->meetingTimeInfo = $meetingTimeInfo;
}
}
?>