<?php
/*
 * Copyright 2005 - 2012  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�s meeting object, by 0x00000001. If this property did not previously exist on the organizer�s meeting object, it MUST be set with a value of 0x00000001.
*/
        // 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']];
        $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))
                $entryid = $this->findCalendarItems($goid2, $calFolder);
        } else {
            $entryid = $this->findCalendarItems($goid2, $calFolder);
        }

        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;
                        }

                        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;
    }
}
?>